Step 0 set work directories

set.seed(2020)

Provide directories for training images. Training images and Training fiducial points will be in different subfolders.

train_dir <- "../data/train_set/" # This will be modified for different data sets.
train_image_dir <- paste(train_dir, "images/", sep="")
train_pt_dir <- paste(train_dir,  "points/", sep="")
train_label_path <- paste(train_dir, "label.csv", sep="")

Step 1: set up controls for evaluation experiments.

In this chunk, we have a set of controls for the evaluation experiments.

run.presentation.day <- FALSE #presentation day flag. No training. Generate a csv file
run.cv <- TRUE # run cross-validation on the training set
sample.reweight <- FALSE # run sample reweighting in model training
K <- 5  # number of CV folds
run.feature.train <- TRUE # process features for training set
run.test <- TRUE # run evaluation on an independent test set
run.feature.test <- TRUE # process features for test set

# gbm
run.gbm.train <- FALSE # gbm(imroved) is the chosen advanced model
run.gbm.test <- TRUE # gbm(imroved) is the chosen advanced model
gbm.numtrees <- 1000 #number of trees to use in gbm

#features options
run.poly.feature <- TRUE # process poly features
run.add.poly.feature <- TRUE # and poly features to features matrix

# svm
run.svm <- TRUE # svm is the chosen advanced model
run.svm.train <- FALSE # train svm model
model.selection <- FALSE # perform model selection on svm models
run.svm.test <- TRUE # evaluate performance on the test set

# Random Forest Model with new feature
run.rf <- TRUE
train.rf <- FALSE # Train Random Forest Model
test.rf <- TRUE # Test Random Forest Model

# Random Forest Model with old feature
run.rf.old.feature <- TRUE
train.rf.old.feature <- FALSE
test.rf.old.feature <- TRUE

# ridge
run.ridge <- FALSE # ridge is the chosen advanced model
alpha <- 0 # ridge regression
train.ridge <- FALSE # train ridge model

# PCA + LDA
run.pca_lda <- TRUE # PCA + LDA is the chosen adcanced model
run.select_PC <- FALSE #run different PCs
run.lda <- FALSE # run lda on the training set
run.pca_lad.test <- TRUE # evaluate performance on the test set

Using cross-validation or independent test set evaluation, we compare the performance of models with different specifications.

Step 2: import data and train-test split

#train-test split
info <- read.csv(train_label_path)
n <- nrow(info) #get number of rows from csv
n_train <- round(n*(4/5), 0) #use 4/5 amount of data for training
train_idx <- sample(info$Index, n_train, replace = F) #grab indexes used for training
test_idx <- setdiff(info$Index, train_idx) # get indexes not used for training

Fiducial points are stored in matlab format. In this step, we read them and store them in a list.

run.presentation.day
[1] FALSE

If on presentation day, we will run our baseline and advanced model, and produce a csv file containing label predictions.

Step 3: construct features and responses

Figure1

feature.R is the wrapper for all feature engineering functions and options. The function feature( ) have options that correspond to different scenarios for the project and produces an R object that contains features and responses that are required by all the models that are going to be evaluated later.

source("../lib/feature.R")
tm_feature_train <- NA
gbm_tm_feature_train <- NA
if(run.feature.train){
  tm_feature_train <- system.time(dat_train<-feature(fiducial_pt_list,train_idx,
                                                     run.poly.feature, run.add.poly.feature))
  gbm_tm_feature_train <- system.time(gbm_dat_train<-feature(fiducial_pt_list,train_idx,
                                                             FALSE, FALSE))
  save(gbm_dat_train, file="../output/gbm_feature_train.RData")
  save(dat_train, file="../output/feature_train.RData")
}else{
  #load(file="../output/feature_train.RData")
  #load(file="../output/gbm_feature_train.RData")
}

tm_feature_test <- NA
gbm_tm_feature_test <- NA
if(run.feature.test){
  tm_feature_test <- system.time(dat_test <- feature(fiducial_pt_list, test_idx,
                                                     run.poly.feature, run.add.poly.feature))
  gbm_tm_feature_test <- system.time(gbm_dat_test <- feature(fiducial_pt_list, test_idx,
                                                             FALSE, FALSE))
  save(gbm_dat_test, file="../output/gbm_feature_test.RData")
  save(dat_test, file="../output/feature_test.RData")
}else{
  load(file="../output/feature_test.RData")
  load(file="../output/gbm_feature_test.RData")
}

Step 4: train classification models with training features and responses; run test on test images

Call the train model and test model from library.

train.R and test.R are wrappers for all model training steps and classification/prediction steps.

source("../lib/train.R")
source("../lib/test.R")

Baseline GBM Model

if (run.gbm.train){
  if (sample.reweight){

    gbm_dat_train$label <- as.factor(gbm_dat_train$label)
    dat_train_balanced_gbm <- SMOTE(label ~ ., gbm_dat_train, perc.over = 100, perc.under=200)
    table(dat_train_balanced_gbm$label)

    gbm_tm_train <- system.time(gbm_train <- train_gbm(dat_train_balanced_gbm, s=0.1,
                                                       K=K, n=gbm.numtrees,w = NULL))

  } else {
    gbm_tm_train <- system.time(gbm_train <- train_gbm(gbm_dat_train, s=0.1,
                                                       K=K, n=gbm.numtrees,w = NULL))
  }

  # plot the performance
  best.iter.oob <- gbm.perf(gbm_train,method="OOB") # returns out-of-bag estimated best number of trees
  print(best.iter.oob)
  best.iter.cv <- gbm.perf(gbm_train,method="cv") # returns K-fold cv estimate of best number of trees
  print(best.iter.cv)

  saveRDS(gbm_train, "../output/gbm_model.rds")
  save(gbm_tm_train, best.iter.cv, file="../output/gbm_outputs.RData")
}
if(run.gbm.test){
  set.seed(2020)
  load(file="../output/gbm_outputs.RData")
  gbm_tm_test = NA
  feature_test <- as.matrix(gbm_dat_test[, 1:ncol(gbm_dat_test)-1])

  gbm_train <- readRDS("../output/gbm_model.rds")
  gbm_tm_test <- system.time(prob_pred_baseline<-test_gbm(gbm_train,as.data.frame(feature_test),
                                                          n=best.iter.cv,pred.type = 'response'))

  label_pred_baseline <- colnames(prob_pred_baseline)[apply(prob_pred_baseline, 1, which.max)]
}
if (run.gbm.test){
  load(file="../output/gbm_outputs.RData")
  gbm_accu <- mean(gbm_dat_test$label == label_pred_baseline)
  gbm.auc <- WeightedROC(as.numeric(label_pred_baseline), gbm_dat_test$label)
  gbm_auc = WeightedAUC(gbm.auc)
  cat("Time for constructing gbm training features=", gbm_tm_feature_train[1], "s \n")
  cat("Time for constructing gbm testing features=", gbm_tm_feature_test[1], "s \n")
  cat("The AUC of gbm model is", gbm_auc, ".\n")
  cat("The accuracy of GBM baseline model is", gbm_accu*100, "%.\n")
  cat("Time for training gbm model=", gbm_tm_train[1], "s \n")
  cat("Time for testing model=", gbm_tm_test[1], "s \n")
}
Time for constructing gbm training features= 0.756 s 
Time for constructing gbm testing features= 0.164 s 
The AUC of gbm model is 0.9016105 .
The accuracy of GBM baseline model is 89.33333 %.
Time for training gbm model= 451.15 s 
Time for testing model= 14.304 s 

Advanced Model: Random Forest

The second advanced model is random forest. Here we use the datasets that are extracted by new feature functions. We used two models trained by both imbalanced and balanced dataset. We used SMOTE function to balance both training and testing data. For tuning the model, we set mtry = 308, tree number = 1000, and node size = 30 for the RF model using balanced data (SMOTE), and we set mtry = 308, tree number = 1500, and node size = 30 for the RF model using imbalanced data. The evaluation of the model is shown at the end of this section.

The tuning part is in a separate file named “appendix_tune_rf.rmd” in doc folder. Please feel free to check that to see the tuning process.

We also trained random forest model with the datasets that are extracted by old feature functions. That is in part 6. Thank you for reading!

if(run.rf){
  ## Training RF
  if(train.rf){
    rf_dat_train <- dat_train
    rf_dat_train$label <- as.factor(rf_dat_train$label)
    dat_train_balanced_SMOTE <- SMOTE(label ~ ., rf_dat_train, perc.over = 100, perc.under=200)
    # Train RF by balanced data
    time.rf.train.final.balanced <- system.time(
      random_forest_fit_final_balanced <- random_forest_train(dat_train_balanced_SMOTE,
                                                              mtry = 308, tree_number = 1000,
                                                              node_size = 30))
    save(random_forest_fit_final_balanced, file = "../output/rf_train_final_balanced.RData")
    save(time.rf.train.final.balanced, file = "../output/rf_train_final_time_balanced.RData")
    # Train RF by imbalanced data
    time.rf.train.final.imbalanced <- system.time(
      random_forest_fit_final_imbalanced <- random_forest_train(dat_train,
                                                                mtry = 308, tree_number = 1000,
                                                                node_size = 30))
    save(time.rf.train.final.imbalanced, file = "../output/rf_train_final_time_imbalanced.RData")
    save(random_forest_fit_final_imbalanced, file = "../output/rf_train_final_imbalanced.RData")
  }else{
    load("../output/rf_train_final_balanced.RData")
    load("../output/rf_train_final_time_balanced.RData")
  }
  # Evaluation:
  if(test.rf){
    rf_dat_test <- dat_test
    rf_dat_test$label <- as.numeric(rf_dat_test$label)

    time.rf.test.final.balanced <- system.time(
      rf_predicted_balanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_final_balanced,
                                                                       rf_dat_test))))
    rf_accuracy_balanced <- mean(round(rf_predicted_balanced == rf_dat_test$label))
    tpr.fpr.balanced <- WeightedROC(as.numeric(rf_predicted_balanced),rf_dat_test$label)
    rf_AUC_balanced <- WeightedAUC(tpr.fpr.balanced)

    cat("AUC for tuned Random Forest(balanced): ", rf_AUC_balanced,".\n")
    cat("Accuracy for tuned Random Forest(balanced)", rf_accuracy_balanced*100,"%.\n")
    cat("Training time for tuned Random Forest: ", time.rf.train.final.balanced[1], "s.\n")
    cat("Testing time for tuned Random Forest: ", time.rf.test.final.balanced[1], "s.\n")
    cat("   ","\n")
  }
}
AUC for tuned Random Forest(balanced):  0.9224453 .
Accuracy for tuned Random Forest(balanced) 92.16667 %.
Training time for tuned Random Forest:  810.32 s.
Testing time for tuned Random Forest:  0.596 s.
    

We think RF model do not need Cross-Validation. Here is a snippet from Breiman’s official documentation: In random forests, there is no need for cross-validation or a separate test set to get an unbiased estimate of the test set error. It is estimated internally, during the run, as follows: Each tree is constructed using a different bootstrap sample from the original data. About one-third of the cases are left out of the bootstrap sample and not used in the construction of the kth tree. Put each case left out in the construction of the kth tree down the kth tree to get a classification. In this way, a test set classification is obtained for each case in about one-third of the trees. At the end of the run, take j to be the class that got most of the votes every time case n was oob. The proportion of times that j is not equal to the true class of n averaged over all cases is the oob error estimate. This has proven to be unbiased in many tests.

Alternative Model 1: SVM Model

If the given data set is imbalanced, we apply SMOTE to rebalance the data and use it to train our svm models.

sample.reweight
[1] FALSE

Tuning hyper-parameters for both linear and radial basis kernel and select the kernel method that produces the highest AUC and accuracy among the two methods.

if(run.svm){
  set.seed(2020)
  if(model.selection){
    svm_model_auc <- rep(NA, 2)
    svm_model_accu <- rep(NA, 2)
    ### linear kernel
    if(run.cv){
      best.linear.cost <- svm_linear_cost_tune(svm_training_data)
      cat("The best cost for svm model with linear kernel is: ",
          best.linear.cost$best.parameters$cost) # best cost is 0.01
      svm.linear.train.start = proc.time()
      svm_linear_mod <- svm_linear_train(svm_training_data, 0.01, K)
      svm.linear.train.end = proc.time()
      svm.linear.tm = svm.linear.train.end - svm.linear.train.start
      save(svm_linear_mod, file="../output/svm_linear_mod.RData")
      save(svm.linear.tm, file="../output/tm_svm_linear_mod.RData")
    } else {
      load(file="../output/svm_linear_mod.RData")
    }
    svm_linear_pred <- svm_test(svm_linear_mod, svm_training_data, FALSE)
    # evaluate performance on linear kernel
    svm_model_accu[1] <- mean(round(svm_linear_pred == svm_training_data$label))
    tpr.fpr_linear <- WeightedROC(as.numeric(svm_linear_pred), svm_training_data$label)
    svm_model_auc[1] <- WeightedAUC(tpr.fpr_linear)

    #### radial basis kernel
    if(run.cv){
      best.radial.cost <- svm_radial_cost_tune(svm_training_data)
      svm.rbf.train.start = proc.time()
      svm_radial_mod <- svm_radial_train(svm_training_data, best.radial.cost, K)
      svm.rbf.train.end = proc.time()
      svm.rbf.tm = svm.rbf.train.end - svm.rbf.train.start
      save(svm_radial_mod, file="../output/svm_radial_mod.RData")
      save(svm.rbf.tm, file="../output/tm_svm_radial_mod.RData")
    } else {
      load(file="../output/svm_radial_mod.RData")
    }
    svm_radial_pred <- svm_test(svm_radial_mod, svm_training_data, FALSE)
    # evaluate performance on rbf kernel
    svm_model_accu[2] <- mean(round(svm_radial_pred == svm_training_data$label))
    tpr.fpr_rbf <- WeightedROC(as.numeric(svm_radial_pred), svm_training_data$label)
    svm_model_auc[2] <- WeightedAUC(tpr.fpr_rbf)

    # table to display results for the two kernel methods
    svm_res = matrix(rep(NA,6),ncol=3)
    svm_res[,1] = svm_model_accu
    svm_res[,2] = svm_model_auc
    svm_res[,3] = c(svm.linear.tm[[3]], svm.rbf.tm[[3]])
    colnames(svm_res) = c("Accuracy", "AUC","Running Time")
    rownames(svm_res) = c("linear","radial basis")
    save(svm_res, file="../output/svm_model_selection.RData")
  } else {
    if(run.presentation.day){
      tm_svm_radial_mod < system.time(svm_radial_mod <- svm_radial_train(svm_training_data,
                                                                         1, K))
    } else {
      load(file="../output/svm_radial_mod.RData")
      load(file="../output/svm_model_selection.RData")
      svm_res
    }
  }
}
              Accuracy       AUC Running Time
linear       0.9224402 0.9216738      423.341
radial basis 0.9573881 0.9557403      523.557

Since radial basis kernel has higher accuracy and AUC than linear kernel, we will choose radial basis as our kernel method for training the svm model.

if(run.svm){
  tm_svm_rbf_test <- NA
  svm_testing_data <- dat_test
  if(run.svm.test){
    if(!run.presentation.day) {
      tm_svm_rbf_test <- system.time(svm_rbf_pred <- svm_test(svm_radial_mod, svm_testing_data, FALSE))
      svm_test_accu = mean(round(svm_rbf_pred == svm_testing_data$label))
      tpr.fpr.rbf <- WeightedROC(as.numeric(svm_rbf_pred), svm_testing_data$label)
      svm_test_auc = WeightedAUC(tpr.fpr.rbf)
      save(tm_svm_rbf_test, file="../output/tm_svm_rbf_test.RData")
  
      cat("The accuracy of svm model is", svm_test_accu*100, "%.\n")
      cat("The AUC of svm model is", svm_test_auc, ".\n")
    } else {
      tm_svm_rbf_test <- system.time(svm_rbf_pred <- svm_test(svm_radial_mod, svm_testing_data))
      svm_test_accu = mean(round(svm_rbf_pred == svm_testing_data$label))
      tpr.fpr.rbf <- WeightedROC(as.numeric(svm_rbf_pred), svm_testing_data$label)
      svm_test_auc = WeightedAUC(tpr.fpr.rbf)
      save(tm_svm_rbf_test, file="../output/tm_svm_rbf_test.RData")
  
      cat("The accuracy of svm model is", svm_test_accu*100, "%.\n")
      cat("The AUC of svm model is", svm_test_auc, ".\n")
    }
  }
}
The accuracy of svm model is 90.5 %.
The AUC of svm model is 0.8768725 .
svm.rbf.tm
   user  system elapsed 
523.557   5.437 521.159 

Alternative Model 2: Ridge Model

if(run.ridge){
  tm_ridge_train <- NA
  if (train.ridge){
    dat_train_rebalanced <- ROSE(label ~ ., data = dat_train, seed=2020)$data
    tm_ridge_train <- system.time(ridge_cv_model<-ridge_train(train_data=dat_train_rebalanced,
                                                              alpha=alpha, K=K, lambda=lambda))
    save(ridge_cv_model, file="../output/ridge_cv_model.RData")
    save(tm_ridge_train, file="../output/ridge_train_time.RData")
  } else {
    load(file="../output/ridge_cv_model.RData")
    load(file="../output/ridge_train_time.RData")
  }
}
if(run.ridge){
  if (train.ridge){
    set.seed(2020)
    dat_train_rebalanced <- ROSE(label ~ ., data = dat_train, seed=2020)$data
    feature_train = as.matrix(dat_train_rebalanced[,-dim(dat_train_rebalanced)[2]])
    label_train = as.integer(dat_train_rebalanced$label)
    ridge_model = cv.glmnet(x=feature_train, y=label_train, alpha=alpha, nfolds=K, lambda=lambda)
    opt_lambda = ridge_model$lambda.min
    save(opt_lambda, file="../output/ridge_optimal_lambda.RData")
  } else {
    load(file="../output/ridge_optimal_lambda.RData")
  }
}
if(run.ridge){
  tm_ridge_test = NA
  if(run.test){
    load("../output/ridge_cv_model.RData")
    dat_test_rebalanced <- dat_test
    feature_test <- as.matrix(dat_test_rebalanced[, -dim(dat_test_rebalanced)[2]])
    tm_ridge_test <- system.time(label_pred<-as.integer(ridge_test(model=ridge_cv_model,
                                                                   features=feature_test,
                                                                   pred.type = 'class')))
    save(tm_ridge_test, file="../output/ridge_test_time.RData")
  } else{
    load(file="../output/ridge_test_time.RData")
  }
}
if(run.ridge){
  cat("Time for constructing training features=", tm_feature_train[1], "s \n")
  cat("Time for constructing testing features=", tm_feature_test[1], "s \n")
  cat("Time for training ridge model=", tm_ridge_train[1], "s \n")
  cat("Time for testing ridge model=", tm_ridge_test[1], "s \n")
}
Time for constructing training features= 27.937 s 
Time for constructing testing features= 7.355 s 
Time for training ridge model= 27.49 s 
Time for testing ridge model= 0.094 s 
if(run.ridge){
  load("../output/ridge_cv_model.RData")
  dat_test_rebalanced <- dat_test
  feature_test <- as.matrix(dat_test_rebalanced[, -dim(dat_test_rebalanced)[2]])
  label_pred = as.integer(predict(ridge_cv_model, s=opt_lambda, newx=feature_test,
                                  type='class'))
  label_test = as.integer(dat_test_rebalanced$label)
  ridge_accuracy = mean(round(label_test== label_pred))
  cat("The accuracy of the ridge model is", ridge_accuracy*100, "%.\n")
  ridge_AUC = auc(roc(label_pred,label_test))
  cat("The AUC of the ridge model is", ridge_AUC, ".\n")
}
The accuracy of the ridge model is 80.5 %.
The AUC of the ridge model is 0.9023372 .

Alternative Model 3: PCA + LDA

      load(balanced_train_data, file="../output/feature_train.RData")
Error in load(balanced_train_data, file = "../output/feature_train.RData") : 
  object 'balanced_train_data' not found

Since there are over 6000 features, we implement the PCA method to reduce dimension according to the covariance matrix. We only retain PCs with large variance.

if(run.pca_lda){
  balanced_test_data <- dat_test
  if(run.select_PC){
    #separate the features from label
    dat_train_new <- balanced_train_data[,-dim(balanced_train_data)[2]]
    dat_test_new <- balanced_test_data[,-dim(balanced_test_data)[2]]
    #create a vector contain target number of PCs
    num.pca <- c(10,50,500,1000)
    train_pca <- function(num.pca){
      for(i in 1:length(num.pca)){
        #start time for training the model
        train.model.start = proc.time()
        #run PCA
        pca <- prcomp(dat_train_new)
        #store for each potential PC
        train_pca <- data.frame(pca$x[,1:num.pca[i]],
                                label = balanced_train_data[dim(balanced_train_data)[2]])
        pred_pca <- predict(pca,dat_test_new)
        test_pca <- data.frame(pred_pca[,1:num.pca[i]],
                               label = balanced_test_data[dim(balanced_test_data)[2]])
        #fitting the lda model
        lda_pca <- lda(label ~ ., data = train_pca)
        #stop time for training the model
        train.model.end = proc.time()
        #start time for testing the model
        test.model.start = proc.time()
        #predict lda model
        lda_pred_pca = predict(lda_pca,test_pca[-dim(test_pca)[2]])
        #end time for testing the model
        test.model.end = proc.time()
        #test accuracy
        test_accuracy=confusionMatrix(lda_pred_pca$class, test_pca$label)$overall[1]
        print(list(l1=train.model.end - train.model.start,
                   l2=test.model.end - test.model.start,
                   l3=test_accuracy))
      }
    }
    train_pca(num.pca)
  }
}

By comparing the training time, test time and accuracy, we use model with 500 PCs.

run.lda.train
[1] TRUE
if(run.pca_lda){
  test.model.start = proc.time()
  pred_train_lda <- predict(lda_pca_500, train_pca_500[-dim(train_pca_500)[2]])
  accu_train_lda <- mean(pred_train_lda$class == train_pca_500$label)
  cat("The trainig accuracy of model: LDA", "is", accu_train_lda*100, "%.\n")
  #calculating the test time
  if(run.test){
    pred_test_lda <- predict(lda_pca_500, test_pca_500)
  }
  test.model.end = proc.time()
  save(pred_test_lda, file="../output/fit_train.RData")
  accu_test_lda <- mean(pred_test_lda$class == test_pca_500$label)
  cat("The accuracy of model: LDA", "is", accu_test_lda*100, "%.\n")
  tpr.fpr <- WeightedROC(as.numeric(pred_test_lda$class), test_pca_500$label)
  lda_auc = WeightedAUC(tpr.fpr)
  cat("The AUC of model: LDA is", lda_auc, ".\n")
}
The trainig accuracy of model: LDA is 89.94048 %.
The accuracy of model: LDA is 74 %.
The AUC of model: LDA is 0.6973767 .

Prediction performance matters, so does the running times for constructing features and for training the model, especially when the computation resource is limited.

if(run.pca_lda){
  tm_train <- train.model.end - train.model.start
  tm_test <- test.model.end - test.model.start
  cat("Time for constructing training features =", tm_feature_train[1], "s \n")
  cat("Time for constructing testing features =", tm_feature_test[1], "s \n")
  cat("Time for training model =", tm_train[1], "s \n")
  cat("Time for testing model =", tm_test[1], "s \n")
}
Time for constructing training features = 27.937 s 
Time for constructing testing features = 7.355 s 
Time for training model = 645.814 s 
Time for testing model = 0.116 s 

Alternative Model 4: Random Forest (RF) Model with old features:

The fourth alternative model is random forest using old features given in the starter code. Here we use the datasets that are extracted by old feature functions. We used two models trained by both imbalanced and balanced dataset. We used ROSE function to balance both training and testing data. For tuning the model, we set mtry = 308, tree number = 500, and node size = 10 for the RF model using balanced data, and we set mtry = 308, tree number = 1500, and node size = 30 for the RF model using imbalanced data. The evaluation of the model is shown at the end of this section, and we will compare this model with the RF model trained by new features.

if(run.rf.old.feature){
  old_feature <- function(input_list = fiducial_pt_list, index){
    old_pairwise_dist <- function(vec){
      return(as.vector(dist(vec)))
    }
    old_pairwise_dist_result <-function(mat){
      return(as.vector(apply(mat, 2, old_pairwise_dist)))
    }
    old_pairwise_dist_feature <- t(sapply(input_list[index], old_pairwise_dist_result))
    dim(old_pairwise_dist_feature)
    old_pairwise_data <- cbind(old_pairwise_dist_feature, info$label[index])
    colnames(old_pairwise_data) <- c(paste("feature", 1:(ncol(old_pairwise_data)-1), sep = ""),
                                     "label")
    old_pairwise_data <- as.data.frame(old_pairwise_data)
    old_pairwise_data$label <- as.factor(old_pairwise_data$label)
    return(feature_df = old_pairwise_data)
  }

  old_tm_feature_train <- NA
  if(run.feature.train){
    old_tm_feature_train <- system.time(old_dat_train <- old_feature(fiducial_pt_list, train_idx))
    save(old_dat_train, file="../output/feature_train_old.RData")
  }else{
    load(file="../output/feature_train_old.RData")
  }
  old_tm_feature_test <- NA
  if(run.feature.test){
    old_tm_feature_test <- system.time(old_dat_test <- old_feature(fiducial_pt_list, test_idx))
    save(old_dat_test, file="../output/feature_test_old.RData")
  }else{
    load(file="../output/feature_test_old.RData")
  }

  # Train Model
  if(train.rf.old.feature){
    # transfer label column from factor to numeric
    old_dat_train$label <- as.numeric(old_dat_train$label)
    old_dat_test$label <- as.numeric(old_dat_test$label)
  
    # Balance data
    dat_train$label <- as.factor(dat_train$label)
    old_dat_train_balanced_SMOTE <- SMOTE(label ~ ., dat_train, perc.over = 100, perc.under=200)
    # Balanced
    old_time.rf.train.final.balanced <- system.time(
      old_random_forest_fit_final_balanced <- old_random_forest_train(old_dat_train_balanced_SMOTE,
                                                                      mtry = 308, tree_number = 1000,
                                                                      node_size = 30))
    save(old_random_forest_fit_final_balanced,
         file = "../output/rf_train_final_balanced_old_feature.RData")
    save(old_time.rf.train.final.balanced,
         file = "../output/rf_train_final_time_balanced_old_feature.RData")
   
  }else{
    load("../output/rf_train_final_balanced_old_feature.RData")
    load("../output/rf_train_final_time_balanced_old_feature.RData")
  }


  # Evaluate Model
  old_rf_dat_test <- old_dat_test
  old_rf_dat_test$label <- as.numeric(old_rf_dat_test$label)
  if(test.rf.old.feature){
    old_time.rf.test.final.balanced <- system.time(
      rf_predicted_balanced <- as.numeric(as.vector(old_random_forest_test(old_random_forest_fit_final_balanced, old_rf_dat_test))))
    old_rf_accuracy_balanced <- mean(round(rf_predicted_balanced == old_rf_dat_test$label))
    old_tpr.fpr.balanced <- WeightedROC(as.numeric(rf_predicted_balanced),old_rf_dat_test$label)
    old_rf_AUC_balanced <- WeightedAUC(old_tpr.fpr.balanced)

    cat("AUC(balanced) for Random Forest with old feature: ", old_rf_AUC_balanced,".\n")
    cat("Accuracy(balanced) for Random Forest with old feature", old_rf_accuracy_balanced*100,"%.\n")
    cat("Training time (balanced) for Random Forest with old feature: ", old_time.rf.train.final.balanced[1], "s.\n")
    cat("Testing time (balanced) for Random Forest with old feature: ", old_time.rf.test.final.balanced[1], "s.\n")
    cat("   ","\n")
  }
}
AUC(balanced) for Random Forest with old feature:  0.9145861 .
Accuracy(balanced) for Random Forest with old feature 88.33333 %.
Training time (balanced) for Random Forest with old feature:  462.69 s.
Testing time (balanced) for Random Forest with old feature:  0.29 s.
    

Generate a csv on presentation day

run.presentation.day
[1] FALSE

Reference

  • Du, S., Tao, Y., & Martinez, A. M. (2014). Compound facial expressions of emotion. Proceedings of the National Academy of Sciences, 111(15), E1454-E1462.
LS0tCnRpdGxlOiAiUHJvamVjdCAzOiBGYWNpYWwgRXhwcmVzc2lvbiBQcmVkaWN0aXZlIE1vZGVsaW5nIgpzdWJ0aXRsZTogIkdyb3VwIDUiCmF1dGhvcjogIkppbmdiaW4gQ2FvLCBDaHVhbmNodWFuIExpdSwgRGVubmlzIFNocGl0cywgWWluZ3lhbyBXdSwgWmlrdW4gWmh1YW5nIgpvdXRwdXQ6CiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIGh0bWxfZG9jdW1lbnQ6CiAgICBkZl9wcmludDogcGFnZWQKLS0tCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojVGVzdCBCcmFuY2ggY3JlYXRlZAppZighcmVxdWlyZSgiUi5tYXRsYWIiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiUi5tYXRsYWIiKQp9CmlmKCFyZXF1aXJlKCJyZWFkeGwiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygicmVhZHhsIikKfQppZighcmVxdWlyZSgiZHBseXIiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiZHBseXIiKQp9CmlmKCFyZXF1aXJlKCJyZWFkeGwiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygicmVhZHhsIikKfQppZighcmVxdWlyZSgiZ2dwbG90MiIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikKfQppZighcmVxdWlyZSgiY2FyZXQiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiY2FyZXQiKQp9CmlmKCFyZXF1aXJlKCJnbG1uZXQiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiZ2xtbmV0IikKfQppZighcmVxdWlyZSgiV2VpZ2h0ZWRST0MiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiV2VpZ2h0ZWRST0MiKQp9CmlmKCFyZXF1aXJlKCJnYm0iKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiZ2JtIikKfQppZighcmVxdWlyZSgiRE13UiIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJETXdSIikKfQppZighcmVxdWlyZSgiT3BlbkltYWdlUiIpKXsKIGluc3RhbGwucGFja2FnZXMoIk9wZW5JbWFnZVIiKQp9CmlmKCFyZXF1aXJlKCJBVUMiKSl7CiBpbnN0YWxsLnBhY2thZ2VzKCJBVUMiKQp9CmlmKCFyZXF1aXJlKCJlMTA3MSIpKXsKIGluc3RhbGwucGFja2FnZXMoImUxMDcxIikKfQppZighcmVxdWlyZSgicmFuZG9tRm9yZXN0IikpewogaW5zdGFsbC5wYWNrYWdlcygicmFuZG9tRm9yZXN0IikKfQppZighcmVxdWlyZSgieGdib29zdCIpKXsKIGluc3RhbGwucGFja2FnZXMoInhnYm9vc3QiKQp9CmlmKCFyZXF1aXJlKCJ0aWJibGUiKSl7CiBpbnN0YWxsLnBhY2thZ2VzKCJ0aWJibGUiKQp9CmlmKCFyZXF1aXJlKCJST1NFIikpewogaW5zdGFsbC5wYWNrYWdlcygiUk9TRSIpCn0KaWYoIXJlcXVpcmUoInRpZHl2ZXJzZSIpKXsKIGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpCn0KaWYoIXJlcXVpcmUoImNhVG9vbHMiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiY2FUb29scyIpCn0KaWYoIXJlcXVpcmUoInByZWRpY3Rpb24iKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygicHJlZGljdGlvbiIpCn0KaWYoIXJlcXVpcmUoInBST0MiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygicFJPQyIpCn0KaWYoIXJlcXVpcmUoIkRNd1IiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiRE13UiIpCn0KaWYoIXJlcXVpcmUoIk1BU1MiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiTUFTUyIpCn0KbGlicmFyeShSLm1hdGxhYikKbGlicmFyeShyZWFkeGwpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShjYXJldCkKbGlicmFyeShnbG1uZXQpCmxpYnJhcnkoV2VpZ2h0ZWRST0MpCmxpYnJhcnkoZ2JtKQpsaWJyYXJ5KERNd1IpCiMjIyBuZXcgbGlicmFyaWVzCmxpYnJhcnkoT3BlbkltYWdlUikKbGlicmFyeShBVUMpCmxpYnJhcnkoZTEwNzEpCmxpYnJhcnkocmFuZG9tRm9yZXN0KQpsaWJyYXJ5KHhnYm9vc3QpCmxpYnJhcnkodGliYmxlKQpsaWJyYXJ5KFJPU0UpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGNhVG9vbHMpCmxpYnJhcnkocHJlZGljdGlvbikKbGlicmFyeShwUk9DKQpsaWJyYXJ5KE1BU1MpCmBgYAoKCiMjIyBTdGVwIDAgc2V0IHdvcmsgZGlyZWN0b3JpZXMKYGBge3Igd2tkaXIsIGV2YWw9RkFMU0V9CnNldC5zZWVkKDIwMjApCmBgYAoKUHJvdmlkZSBkaXJlY3RvcmllcyBmb3IgdHJhaW5pbmcgaW1hZ2VzLiBUcmFpbmluZyBpbWFnZXMgYW5kIFRyYWluaW5nIGZpZHVjaWFsIHBvaW50cyB3aWxsIGJlIGluIGRpZmZlcmVudCBzdWJmb2xkZXJzLgoKYGBge3J9CnRyYWluX2RpciA8LSAiLi4vZGF0YS90cmFpbl9zZXQvIiAjIFRoaXMgd2lsbCBiZSBtb2RpZmllZCBmb3IgZGlmZmVyZW50IGRhdGEgc2V0cy4KdHJhaW5faW1hZ2VfZGlyIDwtIHBhc3RlKHRyYWluX2RpciwgImltYWdlcy8iLCBzZXA9IiIpCnRyYWluX3B0X2RpciA8LSBwYXN0ZSh0cmFpbl9kaXIsICAicG9pbnRzLyIsIHNlcD0iIikKdHJhaW5fbGFiZWxfcGF0aCA8LSBwYXN0ZSh0cmFpbl9kaXIsICJsYWJlbC5jc3YiLCBzZXA9IiIpCmBgYAoKIyMjIFN0ZXAgMTogc2V0IHVwIGNvbnRyb2xzIGZvciBldmFsdWF0aW9uIGV4cGVyaW1lbnRzLgoKSW4gdGhpcyBjaHVuaywgd2UgaGF2ZSBhIHNldCBvZiBjb250cm9scyBmb3IgdGhlIGV2YWx1YXRpb24gZXhwZXJpbWVudHMuCgorIChUL0YpIHdoZXRoZXIgaXQgaXMgb24gcHJlc2VudGF0aW9uIGRheSBmb3IgcHJvZHVjaW5nIHRoZSBjc3YgZmlsZQorIChUL0YpIGNyb3NzLXZhbGlkYXRpb24gb24gdGhlIHRyYWluaW5nIHNldAorIChUL0YpIHJld2VpZ2h0aW5nIHRoZSBzYW1wbGVzIGZvciB0cmFpbmluZyBzZXQKKyAobnVtYmVyKSBLLCB0aGUgbnVtYmVyIG9mIENWIGZvbGRzCisgKFQvRikgcHJvY2VzcyBmZWF0dXJlcyBmb3IgdHJhaW5pbmcgc2V0CisgKFQvRikgcnVuIGV2YWx1YXRpb24gb24gYW4gaW5kZXBlbmRlbnQgdGVzdCBzZXQKKyAoVC9GKSBwcm9jZXNzIGZlYXR1cmVzIGZvciB0ZXN0IHNldAoKKyAoVC9GKSBydW4gZ2JtIGJhc2VsaW5lIG1vZGVsCisgKFQvRikgZXZhbHVhdGUgcGVyZm9ybWFuY2Ugb24gdGhlIHRlc3Qgc2V0CisgKG51bWJlcikgZ2JtLm51bXRyZWVzLCB0aGUgbnVtYmVyIG9mIHRyZWVzIHRvIHVzZSBpbiBHQk0gYmFzZWxpbmUKCisgKFQvRikgcmV0dXJuIHBvbHlub21pYWwgZmVhdHVyZXMgbWF0cml4IG9ubHkKKyAoVC9GKSBhZGQgcG9seW5vbWlhbCBmZWF0dXJlcyB0byBzdGFydGVyIGNvZGUgZmVhdHVyZXMgbWF0cml4CgorIChUL0YpIHJ1biBzdm0gbW9kZWwKKyAoVC9GKSB0cmFpbiBzdm0gbW9kZWwKKyAoVC9GKSBwZXJmb3JtIG1vZGVsIHNlbGVjdGlvbiBvdmVyIGEgbGlzdCBvZiBzdm0gbW9kZWxzCisgKFQvRikgZXZhbHVhdGUgcGVyZm9ybWFuY2Ugb24gdGhlIHRlc3Qgc2V0CgorIChUL0YpIHJ1biByYW5kb20gZm9yZXN0IG1vZGVsCisgKFQvRikgdHJhaW4gcmFuZG9tIGZvcmVzdCBtb2RlbAorIChUL0YpIHJ1biBldmFsdWF0aW9uIG9uIHRoZSB0ZXN0IHNldAoKKyAoVC9GKSBydW4gcmFuZG9tIGZvcmVzdCBtb2RlbCB3aXRoIG9sZCBmZWF0dXJlcworIChUL0YpIHRyYWluIHJhbmRvbSBmb3Jlc3QgbW9kZWwgd2l0aCBvbGQgZmVhdHVyZXMKKyAoVC9GKSBydW4gZXZhbHVhdGlvbiBvbiB0aGUgdGVzdCBzZXQKCisgKFQvRikgcnVuIHJpZGdlIG1vZGVsCisgKDAvMSkgYWxwaGEsIGFscGhhPTAgZm9yIHJpZGdlIHJlZ3Jlc3Npb24sIGFscGhhPTEgZm9yIGxhc3NvIHJlZ3Jlc3Npb24KKyAoVC9GKSB0cmFpbiByaWRnZSBtb2RlbAoKKyAoVC9GKSBydW4gUENBK0xEQSBtb2RlbAorIChUL0YpIHJ1biBkaWZmZXJlbnQgcHJpbmNpcGFsIGNvbXBvbmVudHMKKyAoVC9GKSBydW4gTERBIG9uIHRyYWluaW5nIHNldAorIChUL0YpIHJ1biBldmFsdWF0aW9uIG9uIHRoZSB0ZXN0IHNldAoKYGBge3IgZXhwX3NldHVwfQpydW4ucHJlc2VudGF0aW9uLmRheSA8LSBGQUxTRSAjcHJlc2VudGF0aW9uIGRheSBmbGFnLiBObyB0cmFpbmluZy4gR2VuZXJhdGUgYSBjc3YgZmlsZQpydW4uY3YgPC0gVFJVRSAjIHJ1biBjcm9zcy12YWxpZGF0aW9uIG9uIHRoZSB0cmFpbmluZyBzZXQKc2FtcGxlLnJld2VpZ2h0IDwtIEZBTFNFICMgcnVuIHNhbXBsZSByZXdlaWdodGluZyBpbiBtb2RlbCB0cmFpbmluZwpLIDwtIDUgICMgbnVtYmVyIG9mIENWIGZvbGRzCnJ1bi5mZWF0dXJlLnRyYWluIDwtIEZBTFNFICMgcHJvY2VzcyBmZWF0dXJlcyBmb3IgdHJhaW5pbmcgc2V0CnJ1bi50ZXN0IDwtIFRSVUUgIyBydW4gZXZhbHVhdGlvbiBvbiBhbiBpbmRlcGVuZGVudCB0ZXN0IHNldApydW4uZmVhdHVyZS50ZXN0IDwtIFRSVUUgIyBwcm9jZXNzIGZlYXR1cmVzIGZvciB0ZXN0IHNldAoKIyBnYm0KcnVuLmdibS50cmFpbiA8LSBUUlVFICMgZ2JtKGltcm92ZWQpIGlzIHRoZSBjaG9zZW4gYWR2YW5jZWQgbW9kZWwKcnVuLmdibS50ZXN0IDwtIFRSVUUgIyBnYm0oaW1yb3ZlZCkgaXMgdGhlIGNob3NlbiBhZHZhbmNlZCBtb2RlbApnYm0ubnVtdHJlZXMgPC0gMTAwMCAjbnVtYmVyIG9mIHRyZWVzIHRvIHVzZSBpbiBnYm0KCiNmZWF0dXJlcyBvcHRpb25zCnJ1bi5wb2x5LmZlYXR1cmUgPC0gVFJVRSAjIHByb2Nlc3MgcG9seSBmZWF0dXJlcwpydW4uYWRkLnBvbHkuZmVhdHVyZSA8LSBUUlVFICMgYW5kIHBvbHkgZmVhdHVyZXMgdG8gZmVhdHVyZXMgbWF0cml4CgojIHN2bQpydW4uc3ZtIDwtIEZBTFNFICMgc3ZtIGlzIHRoZSBjaG9zZW4gYWR2YW5jZWQgbW9kZWwKcnVuLnN2bS50cmFpbiA8LSBGQUxTRSAjIHRyYWluIHN2bSBtb2RlbAptb2RlbC5zZWxlY3Rpb24gPC0gRkFMU0UgIyBwZXJmb3JtIG1vZGVsIHNlbGVjdGlvbiBvbiBzdm0gbW9kZWxzCnJ1bi5zdm0udGVzdCA8LSBUUlVFICMgZXZhbHVhdGUgcGVyZm9ybWFuY2Ugb24gdGhlIHRlc3Qgc2V0CgojIFJhbmRvbSBGb3Jlc3QgTW9kZWwgd2l0aCBuZXcgZmVhdHVyZQpydW4ucmYgPC0gVFJVRQp0cmFpbi5yZiA8LSBGQUxTRSAjIFRyYWluIFJhbmRvbSBGb3Jlc3QgTW9kZWwKdGVzdC5yZiA8LSBUUlVFICMgVGVzdCBSYW5kb20gRm9yZXN0IE1vZGVsCgojIFJhbmRvbSBGb3Jlc3QgTW9kZWwgd2l0aCBvbGQgZmVhdHVyZQpydW4ucmYub2xkLmZlYXR1cmUgPC0gRkFMU0UgIyBydW4gcmFuZG9tIGZvcmVzdCB1c2luZyBvbGQgZmVhdHVyZXMKdHJhaW4ucmYub2xkLmZlYXR1cmUgPC0gRkFMU0UgIyB0cmFpbiByYW5kb20gZm9yZXN0IHdpdGggb2xkIGZlYXR1cmVzCnRlc3QucmYub2xkLmZlYXR1cmUgPC0gVFJVRSAjIGV2YWx1YXRlIHBlcmZvcm1hbmNlIG9uIHRlc3Qgc2V0CgojIHJpZGdlCnJ1bi5yaWRnZSA8LSBGQUxTRSAjIHJpZGdlIGlzIHRoZSBjaG9zZW4gYWR2YW5jZWQgbW9kZWwKYWxwaGEgPC0gMCAjIHJpZGdlIHJlZ3Jlc3Npb24gYWxwaGEKbGFtYmRhIDwtIDEwXnNlcSgxMCwgLTIsIGxlbmd0aCA9IDEwMCkgIyBsYW1iZGEKdHJhaW4ucmlkZ2UgPC0gRkFMU0UgIyB0cmFpbiByaWRnZSBtb2RlbAoKIyBQQ0EgKyBMREEKcnVuLnBjYV9sZGEgPC0gRkFMU0UgIyBQQ0EgKyBMREEgaXMgdGhlIGNob3NlbiBhZGNhbmNlZCBtb2RlbApydW4uc2VsZWN0X1BDIDwtIEZBTFNFICNydW4gZGlmZmVyZW50IFBDcwpydW4ubGRhLnRyYWluIDwtIEZBTFNFICMgcnVuIGxkYSBvbiB0aGUgdHJhaW5pbmcgc2V0CnJ1bi5wY2FfbGFkLnRlc3QgPC0gVFJVRSAjIGV2YWx1YXRlIHBlcmZvcm1hbmNlIG9uIHRoZSB0ZXN0IHNldApgYGAKClVzaW5nIGNyb3NzLXZhbGlkYXRpb24gb3IgaW5kZXBlbmRlbnQgdGVzdCBzZXQgZXZhbHVhdGlvbiwgd2UgY29tcGFyZSB0aGUgcGVyZm9ybWFuY2Ugb2YgbW9kZWxzIHdpdGggZGlmZmVyZW50IHNwZWNpZmljYXRpb25zLgoKIyMjIFN0ZXAgMjogaW1wb3J0IGRhdGEgYW5kIHRyYWluLXRlc3Qgc3BsaXQKYGBge3J9CiN0cmFpbi10ZXN0IHNwbGl0CmluZm8gPC0gcmVhZC5jc3YodHJhaW5fbGFiZWxfcGF0aCkKbiA8LSBucm93KGluZm8pICNnZXQgbnVtYmVyIG9mIHJvd3MgZnJvbSBjc3YKbl90cmFpbiA8LSByb3VuZChuKig0LzUpLCAwKSAjdXNlIDQvNSBhbW91bnQgb2YgZGF0YSBmb3IgdHJhaW5pbmcKdHJhaW5faWR4IDwtIHNhbXBsZShpbmZvJEluZGV4LCBuX3RyYWluLCByZXBsYWNlID0gRikgI2dyYWIgaW5kZXhlcyB1c2VkIGZvciB0cmFpbmluZwp0ZXN0X2lkeCA8LSBzZXRkaWZmKGluZm8kSW5kZXgsIHRyYWluX2lkeCkgIyBnZXQgaW5kZXhlcyBub3QgdXNlZCBmb3IgdHJhaW5pbmcKYGBgCgpGaWR1Y2lhbCBwb2ludHMgYXJlIHN0b3JlZCBpbiBtYXRsYWIgZm9ybWF0LiBJbiB0aGlzIHN0ZXAsIHdlIHJlYWQgdGhlbSBhbmQgc3RvcmUgdGhlbSBpbiBhIGxpc3QuCgpgYGB7ciByZWFkIGZpZHVjaWFsIHBvaW50c30KI2Z1bmN0aW9uIHRvIHJlYWQgZmlkdWNpYWwgcG9pbnRzCiNpbnB1dDogaW5kZXgKI291dHB1dDogbWF0cml4IG9mIGZpZHVjaWFsIHBvaW50cyBjb3JyZXNwb25kaW5nIHRvIHRoZSBpbmRleApuX2ZpbGVzIDwtIGxlbmd0aChsaXN0LmZpbGVzKHRyYWluX2ltYWdlX2RpciwnKmpwZycpKQpyZWFkTWF0Lm1hdHJpeCA8LSBmdW5jdGlvbihpbmRleCl7CiAgICAgcmV0dXJuKHJvdW5kKHJlYWRNYXQocGFzdGUwKHRyYWluX3B0X2Rpciwgc3ByaW50ZigiJTA0ZCIsIGluZGV4KSwgIi5tYXQiKSlbWzFdXSwwKSkKfQoKaWYgKHJ1bi5wcmVzZW50YXRpb24uZGF5KXsKICB0ZXN0X2lkeCA8LSBjKDE6bl9maWxlcykgI3NhbXBsZShuX2ZpbGVzLCBuX2ZpbGVzLCByZXBsYWNlID0gRikKICBydW4uZ2JtLnRyYWluIDwtIEZBTFNFCiAgcnVuLmZlYXR1cmUudHJhaW4gPC0gRkFMU0UKICBydW4uZ2JtLnRlc3QgPC0gVFJVRQoKICBydW4ucmYgPC0gVFJVRQogIHRyYWluLnJmIDwtIEZBTFNFCiAgdGVzdC5yZiA8LSBUUlVFCn0KCiNsb2FkIGZpZHVjaWFsIHBvaW50cwpmaWR1Y2lhbF9wdF9saXN0IDwtIGxhcHBseSgxOm5fZmlsZXMsIHJlYWRNYXQubWF0cml4KQpzYXZlKGZpZHVjaWFsX3B0X2xpc3QsIGZpbGU9Ii4uL291dHB1dC9maWR1Y2lhbF9wdF9saXN0LlJEYXRhIikKYGBgCgpJZiBvbiBwcmVzZW50YXRpb24gZGF5LCB3ZSB3aWxsIHJ1biBvdXIgYmFzZWxpbmUgYW5kIGFkdmFuY2VkIG1vZGVsLCBhbmQgcHJvZHVjZSBhIGNzdiBmaWxlIGNvbnRhaW5pbmcgbGFiZWwgcHJlZGljdGlvbnMuCgojIyMgU3RlcCAzOiBjb25zdHJ1Y3QgZmVhdHVyZXMgYW5kIHJlc3BvbnNlcwoKKyBUaGUgZm9sbG93IHBsb3RzIHNob3cgaG93IHBhaXJ3aXNlIGRpc3RhbmNlIGJldHdlZW4gZmlkdWNpYWwgcG9pbnRzIGNhbiB3b3JrIGFzIGZlYXR1cmUgZm9yIGZhY2lhbCBlbW90aW9uIHJlY29nbml0aW9uLgoKICArIEluIHRoZSBmaXJzdCBjb2x1bW4sIDc4IGZpZHVjaWFscyBwb2ludHMgb2YgZWFjaCBlbW90aW9uIGFyZSBtYXJrZWQgaW4gb3JkZXIuCiAgKyBJbiB0aGUgc2Vjb25kIGNvbHVtbiBkaXN0cmlidXRpb25zIG9mIHZlcnRpY2FsIGRpc3RhbmNlIGJldHdlZW4gcmlnaHQgcHVwaWwoMSkgYW5kICByaWdodCBicm93IHBlYWsoMjEpIGFyZSBzaG93biBpbiAgaGlzdG9ncmFtcy4gRm9yIGV4YW1wbGUsIHRoZSBkaXN0YW5jZSBvZiBhbiBhbmdyeSBmYWNlIHRlbmRzIHRvIGJlIHNob3J0ZXIgdGhhbiB0aGF0IG9mIGEgc3VycHJpc2VkIGZhY2UuCiAgKyBUaGUgdGhpcmQgY29sdW1uIGlzIHRoZSBkaXN0cmlidXRpb25zIG9mIHZlcnRpY2FsIGRpc3RhbmNlcyBiZXR3ZWVuIHJpZ2h0IG1vdXRoIGNvcm5lcig1MCkKYW5kIHRoZSBtaWRwb2ludCBvZiB0aGUgdXBwZXIgbGlwKDUyKS4gIEZvciBleGFtcGxlLCB0aGUgZGlzdGFuY2Ugb2YgYW4gaGFwcHkgZmFjZSB0ZW5kcyB0byBiZSBzaG9ydGVyIHRoYW4gdGhhdCBvZiBhIHNhZCBmYWNlLgoKIVtGaWd1cmUxXSguLi9maWdzL2ZlYXR1cmVfdmlzdWFsaXphdGlvbi5qcGcpCgpgZmVhdHVyZS5SYCBpcyB0aGUgd3JhcHBlciBmb3IgYWxsIGZlYXR1cmUgZW5naW5lZXJpbmcgZnVuY3Rpb25zIGFuZCBvcHRpb25zLiBUaGUgZnVuY3Rpb24gYGZlYXR1cmUoIClgIGhhdmUgb3B0aW9ucyB0aGF0IGNvcnJlc3BvbmQgdG8gZGlmZmVyZW50IHNjZW5hcmlvcyBmb3IgdGhlIHByb2plY3QgYW5kIHByb2R1Y2VzIGFuIFIgb2JqZWN0IHRoYXQgY29udGFpbnMgZmVhdHVyZXMgYW5kIHJlc3BvbnNlcyB0aGF0IGFyZSByZXF1aXJlZCBieSBhbGwgdGhlIG1vZGVscyB0aGF0IGFyZSBnb2luZyB0byBiZSBldmFsdWF0ZWQgbGF0ZXIuCgogICsgYGZlYXR1cmUuUmAKICArIElucHV0OiBsaXN0IG9mIGltYWdlcyBvciBmaWR1Y2lhbCBwb2ludAogICsgT3V0cHV0OiBhbiBSRGF0YSBmaWxlIHRoYXQgY29udGFpbnMgZXh0cmFjdGVkIGZlYXR1cmVzIGFuZCBjb3JyZXNwb25kaW5nIHJlc3BvbnNlcwoKYGBge3IgZmVhdHVyZX0Kc291cmNlKCIuLi9saWIvZmVhdHVyZS5SIikKdG1fZmVhdHVyZV90cmFpbiA8LSBOQQpnYm1fdG1fZmVhdHVyZV90cmFpbiA8LSBOQQppZihydW4uZmVhdHVyZS50cmFpbil7CiAgdG1fZmVhdHVyZV90cmFpbiA8LSBzeXN0ZW0udGltZShkYXRfdHJhaW48LWZlYXR1cmUoZmlkdWNpYWxfcHRfbGlzdCx0cmFpbl9pZHgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcnVuLnBvbHkuZmVhdHVyZSwgcnVuLmFkZC5wb2x5LmZlYXR1cmUpKQogIGdibV90bV9mZWF0dXJlX3RyYWluIDwtIHN5c3RlbS50aW1lKGdibV9kYXRfdHJhaW48LWZlYXR1cmUoZmlkdWNpYWxfcHRfbGlzdCx0cmFpbl9pZHgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGQUxTRSwgRkFMU0UpKQogIHNhdmUoZ2JtX2RhdF90cmFpbiwgZmlsZT0iLi4vb3V0cHV0L2dibV9mZWF0dXJlX3RyYWluLlJEYXRhIikKICBzYXZlKGRhdF90cmFpbiwgZmlsZT0iLi4vb3V0cHV0L2ZlYXR1cmVfdHJhaW4uUkRhdGEiKQp9ZWxzZXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3RyYWluLlJEYXRhIikKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9nYm1fZmVhdHVyZV90cmFpbi5SRGF0YSIpCn0KCnRtX2ZlYXR1cmVfdGVzdCA8LSBOQQpnYm1fdG1fZmVhdHVyZV90ZXN0IDwtIE5BCmlmKHJ1bi5mZWF0dXJlLnRlc3QpewogIHRtX2ZlYXR1cmVfdGVzdCA8LSBzeXN0ZW0udGltZShkYXRfdGVzdCA8LSBmZWF0dXJlKGZpZHVjaWFsX3B0X2xpc3QsIHRlc3RfaWR4LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJ1bi5wb2x5LmZlYXR1cmUsIHJ1bi5hZGQucG9seS5mZWF0dXJlKSkKICBnYm1fdG1fZmVhdHVyZV90ZXN0IDwtIHN5c3RlbS50aW1lKGdibV9kYXRfdGVzdCA8LSBmZWF0dXJlKGZpZHVjaWFsX3B0X2xpc3QsIHRlc3RfaWR4LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRkFMU0UsIEZBTFNFKSkKICBzYXZlKGdibV9kYXRfdGVzdCwgZmlsZT0iLi4vb3V0cHV0L2dibV9mZWF0dXJlX3Rlc3QuUkRhdGEiKQogIHNhdmUoZGF0X3Rlc3QsIGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3Rlc3QuUkRhdGEiKQp9ZWxzZXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3Rlc3QuUkRhdGEiKQogIGxvYWQoZmlsZT0iLi4vb3V0cHV0L2dibV9mZWF0dXJlX3Rlc3QuUkRhdGEiKQp9CmBgYAoKIyMjIFN0ZXAgNDogdHJhaW4gY2xhc3NpZmljYXRpb24gbW9kZWxzIHdpdGggdHJhaW5pbmcgZmVhdHVyZXMgYW5kIHJlc3BvbnNlczsgcnVuIHRlc3Qgb24gdGVzdCBpbWFnZXMKCkNhbGwgdGhlIHRyYWluIG1vZGVsIGFuZCB0ZXN0IG1vZGVsIGZyb20gbGlicmFyeS4KCmB0cmFpbi5SYCBhbmQgYHRlc3QuUmAgYXJlIHdyYXBwZXJzIGZvciBhbGwgbW9kZWwgdHJhaW5pbmcgc3RlcHMgYW5kIGNsYXNzaWZpY2F0aW9uL3ByZWRpY3Rpb24gc3RlcHMuCgorIGB0cmFpbi5SYAogICsgSW5wdXQ6IGEgZGF0YSBmcmFtZSBjb250YWluaW5nIGZlYXR1cmVzIGFuZCBsYWJlbHMgYW5kIGEgcGFyYW1ldGVyIGxpc3QuCiAgKyBPdXRwdXQ6YSB0cmFpbmVkIG1vZGVsCisgYHRlc3QuUmAKICArIElucHV0OiB0aGUgZml0dGVkIGNsYXNzaWZpY2F0aW9uIG1vZGVsIHVzaW5nIHRyYWluaW5nIGRhdGEgYW5kIHByb2Nlc3NlZCBmZWF0dXJlcyBmcm9tIHRlc3RpbmcgaW1hZ2VzCiAgKyBJbnB1dDogYW4gUiBvYmplY3QgdGhhdCBjb250YWlucyBhIHRyYWluZWQgY2xhc3NpZmllci4KICArIE91dHB1dDogdHJhaW5pbmcgbW9kZWwgc3BlY2lmaWNhdGlvbgoKKyBJbiB0aGlzIFN0YXJ0ZXIgQ29kZSwgd2UgdXNlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gd2l0aCBMQVNTTyBwZW5hbHR5IHRvIGRvIGNsYXNzaWZpY2F0aW9uLgoKYGBge3IgbG9hZGxpYn0Kc291cmNlKCIuLi9saWIvdHJhaW4uUiIpCnNvdXJjZSgiLi4vbGliL3Rlc3QuUiIpCmBgYAoKIyBCYXNlbGluZSBHQk0gTW9kZWwKCiogTW9kZWwgVHJhaW5pbmcKCmBgYHtyfQppZiAocnVuLmdibS50cmFpbil7CiAgaWYgKHNhbXBsZS5yZXdlaWdodCl7CgogICAgZ2JtX2RhdF90cmFpbiRsYWJlbCA8LSBhcy5mYWN0b3IoZ2JtX2RhdF90cmFpbiRsYWJlbCkKICAgIGRhdF90cmFpbl9iYWxhbmNlZF9nYm0gPC0gU01PVEUobGFiZWwgfiAuLCBnYm1fZGF0X3RyYWluLCBwZXJjLm92ZXIgPSAxMDAsIHBlcmMudW5kZXI9MjAwKQogICAgdGFibGUoZGF0X3RyYWluX2JhbGFuY2VkX2dibSRsYWJlbCkKCiAgICBnYm1fdG1fdHJhaW4gPC0gc3lzdGVtLnRpbWUoZ2JtX3RyYWluIDwtIHRyYWluX2dibShkYXRfdHJhaW5fYmFsYW5jZWRfZ2JtLCBzPTAuMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEs9Sywgbj1nYm0ubnVtdHJlZXMsdyA9IE5VTEwpKQoKICB9IGVsc2UgewogICAgZ2JtX3RtX3RyYWluIDwtIHN5c3RlbS50aW1lKGdibV90cmFpbiA8LSB0cmFpbl9nYm0oZ2JtX2RhdF90cmFpbiwgcz0wLjEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBLPUssIG49Z2JtLm51bXRyZWVzLHcgPSBOVUxMKSkKICB9CgogICMgcGxvdCB0aGUgcGVyZm9ybWFuY2UKICBiZXN0Lml0ZXIub29iIDwtIGdibS5wZXJmKGdibV90cmFpbixtZXRob2Q9Ik9PQiIpICMgcmV0dXJucyBvdXQtb2YtYmFnIGVzdGltYXRlZCBiZXN0IG51bWJlciBvZiB0cmVlcwogIHByaW50KGJlc3QuaXRlci5vb2IpCiAgYmVzdC5pdGVyLmN2IDwtIGdibS5wZXJmKGdibV90cmFpbixtZXRob2Q9ImN2IikgIyByZXR1cm5zIEstZm9sZCBjdiBlc3RpbWF0ZSBvZiBiZXN0IG51bWJlciBvZiB0cmVlcwogIHByaW50KGJlc3QuaXRlci5jdikKCiAgc2F2ZVJEUyhnYm1fdHJhaW4sICIuLi9vdXRwdXQvZ2JtX21vZGVsLnJkcyIpCiAgc2F2ZShnYm1fdG1fdHJhaW4sIGJlc3QuaXRlci5jdiwgZmlsZT0iLi4vb3V0cHV0L2dibV9vdXRwdXRzLlJEYXRhIikKfQoKYGBgCgoqIEV2YWx1YXRpb24gb24gVGVzdCBTZXQKCmBgYHtyfQppZihydW4uZ2JtLnRlc3QpewogIGxvYWQoZmlsZT0iLi4vb3V0cHV0L2dibV9vdXRwdXRzLlJEYXRhIikKICBnYm1fdG1fdGVzdCA9IE5BCiAgZmVhdHVyZV90ZXN0IDwtIGFzLm1hdHJpeChnYm1fZGF0X3Rlc3RbLCAxOm5jb2woZ2JtX2RhdF90ZXN0KS0xXSkKCiAgZ2JtX3RyYWluIDwtIHJlYWRSRFMoIi4uL291dHB1dC9nYm1fbW9kZWwucmRzIikKICBnYm1fdG1fdGVzdCA8LSBzeXN0ZW0udGltZShwcm9iX3ByZWRfYmFzZWxpbmU8LXRlc3RfZ2JtKGdibV90cmFpbixhcy5kYXRhLmZyYW1lKGZlYXR1cmVfdGVzdCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuPWJlc3QuaXRlci5jdixwcmVkLnR5cGUgPSAncmVzcG9uc2UnKSkKCiAgbGFiZWxfcHJlZF9iYXNlbGluZSA8LSBjb2xuYW1lcyhwcm9iX3ByZWRfYmFzZWxpbmUpW2FwcGx5KHByb2JfcHJlZF9iYXNlbGluZSwgMSwgd2hpY2gubWF4KV0KfQpgYGAKCiogU2hvdyBHQk0gQWNjdXJhY3kgYW5kIEFVQwoKYGBge3J9CmlmIChydW4uZ2JtLnRlc3QpewogIGxvYWQoZmlsZT0iLi4vb3V0cHV0L2dibV9vdXRwdXRzLlJEYXRhIikKICBnYm1fYWNjdSA8LSBtZWFuKGdibV9kYXRfdGVzdCRsYWJlbCA9PSBsYWJlbF9wcmVkX2Jhc2VsaW5lKQogIGdibS5hdWMgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhsYWJlbF9wcmVkX2Jhc2VsaW5lKSwgZ2JtX2RhdF90ZXN0JGxhYmVsKQogIGdibV9hdWMgPSBXZWlnaHRlZEFVQyhnYm0uYXVjKQogIGNhdCgiVGltZSBmb3IgY29uc3RydWN0aW5nIGdibSB0cmFpbmluZyBmZWF0dXJlcz0iLCBnYm1fdG1fZmVhdHVyZV90cmFpblsxXSwgInMgXG4iKQogIGNhdCgiVGltZSBmb3IgY29uc3RydWN0aW5nIGdibSB0ZXN0aW5nIGZlYXR1cmVzPSIsIGdibV90bV9mZWF0dXJlX3Rlc3RbMV0sICJzIFxuIikKICBjYXQoIlRoZSBBVUMgb2YgZ2JtIG1vZGVsIGlzIiwgZ2JtX2F1YywgIi5cbiIpCiAgY2F0KCJUaGUgYWNjdXJhY3kgb2YgR0JNIGJhc2VsaW5lIG1vZGVsIGlzIiwgZ2JtX2FjY3UqMTAwLCAiJS5cbiIpCiAgY2F0KCJUaW1lIGZvciB0cmFpbmluZyBnYm0gbW9kZWw9IiwgZ2JtX3RtX3RyYWluWzFdLCAicyBcbiIpCiAgY2F0KCJUaW1lIGZvciB0ZXN0aW5nIG1vZGVsPSIsIGdibV90bV90ZXN0WzFdLCAicyBcbiIpCn0KYGBgCgoKCiMgQWR2YW5jZWQgTW9kZWw6IFJhbmRvbSBGb3Jlc3QKClRoZSBzZWNvbmQgYWR2YW5jZWQgbW9kZWwgaXMgcmFuZG9tIGZvcmVzdC4gSGVyZSB3ZSB1c2UgdGhlIGRhdGFzZXRzIHRoYXQgYXJlIGV4dHJhY3RlZCBieSBuZXcgZmVhdHVyZSBmdW5jdGlvbnMuIFdlIHVzZWQgdHdvIG1vZGVscyB0cmFpbmVkIGJ5IGJvdGggaW1iYWxhbmNlZCBhbmQgYmFsYW5jZWQgZGF0YXNldC4gV2UgdXNlZCBTTU9URSBmdW5jdGlvbiB0byBiYWxhbmNlIGJvdGggdHJhaW5pbmcgYW5kIHRlc3RpbmcgZGF0YS4gRm9yIHR1bmluZyB0aGUgbW9kZWwsIHdlIHNldCBtdHJ5ID0gMzA4LCB0cmVlIG51bWJlciA9IDEwMDAsIGFuZCBub2RlIHNpemUgPSAzMCBmb3IgdGhlIFJGIG1vZGVsIHVzaW5nIGJhbGFuY2VkIGRhdGEgKFNNT1RFKSwgYW5kIHdlIHNldCBtdHJ5ID0gMzA4LCB0cmVlIG51bWJlciA9IDE1MDAsIGFuZCBub2RlIHNpemUgPSAzMCBmb3IgdGhlIFJGIG1vZGVsIHVzaW5nIGltYmFsYW5jZWQgZGF0YS4gVGhlIGV2YWx1YXRpb24gb2YgdGhlIG1vZGVsIGlzIHNob3duIGF0IHRoZSBlbmQgb2YgdGhpcyBzZWN0aW9uLiAgCgpUaGUgdHVuaW5nIHBhcnQgaXMgaW4gYSBzZXBhcmF0ZSBmaWxlIG5hbWVkICJhcHBlbmRpeF90dW5lX3JmLnJtZCIgaW4gZG9jIGZvbGRlci4gUGxlYXNlIGZlZWwgZnJlZSB0byBjaGVjayB0aGF0IHRvIHNlZSB0aGUgdHVuaW5nIHByb2Nlc3MuICAKCldlIGFsc28gdHJhaW5lZCByYW5kb20gZm9yZXN0IG1vZGVsIHdpdGggdGhlIGRhdGFzZXRzIHRoYXQgYXJlIGV4dHJhY3RlZCBieSBvbGQgZmVhdHVyZSBmdW5jdGlvbnMuIFRoYXQgaXMgaW4gcGFydCA2LiBUaGFuayB5b3UgZm9yIHJlYWRpbmchCgpgYGB7cn0KaWYocnVuLnJmKXsKICAjIyBUcmFpbmluZyBSRgogIGlmKHRyYWluLnJmKXsKICAgIHJmX2RhdF90cmFpbiA8LSBkYXRfdHJhaW4KICAgIHJmX2RhdF90cmFpbiRsYWJlbCA8LSBhcy5mYWN0b3IocmZfZGF0X3RyYWluJGxhYmVsKQogICAgZGF0X3RyYWluX2JhbGFuY2VkX1NNT1RFIDwtIFNNT1RFKGxhYmVsIH4gLiwgcmZfZGF0X3RyYWluLCBwZXJjLm92ZXIgPSAxMDAsIHBlcmMudW5kZXI9MjAwKQogICAgIyBUcmFpbiBSRiBieSBiYWxhbmNlZCBkYXRhCiAgICB0aW1lLnJmLnRyYWluLmZpbmFsLmJhbGFuY2VkIDwtIHN5c3RlbS50aW1lKAogICAgICByYW5kb21fZm9yZXN0X2ZpdF9maW5hbF9iYWxhbmNlZCA8LSByYW5kb21fZm9yZXN0X3RyYWluKGRhdF90cmFpbl9iYWxhbmNlZF9TTU9URSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gMzA4LCB0cmVlX251bWJlciA9IDEwMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm9kZV9zaXplID0gMzApKQogICAgc2F2ZShyYW5kb21fZm9yZXN0X2ZpdF9maW5hbF9iYWxhbmNlZCwgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fZmluYWxfYmFsYW5jZWQuUkRhdGEiKQogICAgc2F2ZSh0aW1lLnJmLnRyYWluLmZpbmFsLmJhbGFuY2VkLCBmaWxlID0gIi4uL291dHB1dC9yZl90cmFpbl9maW5hbF90aW1lX2JhbGFuY2VkLlJEYXRhIikKICAgICMgVHJhaW4gUkYgYnkgaW1iYWxhbmNlZCBkYXRhCiAgICB0aW1lLnJmLnRyYWluLmZpbmFsLmltYmFsYW5jZWQgPC0gc3lzdGVtLnRpbWUoCiAgICAgIHJhbmRvbV9mb3Jlc3RfZml0X2ZpbmFsX2ltYmFsYW5jZWQgPC0gcmFuZG9tX2ZvcmVzdF90cmFpbihkYXRfdHJhaW4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gMzA4LCB0cmVlX251bWJlciA9IDEwMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBub2RlX3NpemUgPSAzMCkpCiAgICBzYXZlKHRpbWUucmYudHJhaW4uZmluYWwuaW1iYWxhbmNlZCwgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fZmluYWxfdGltZV9pbWJhbGFuY2VkLlJEYXRhIikKICAgIHNhdmUocmFuZG9tX2ZvcmVzdF9maXRfZmluYWxfaW1iYWxhbmNlZCwgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fZmluYWxfaW1iYWxhbmNlZC5SRGF0YSIpCiAgfWVsc2V7CiAgICBsb2FkKCIuLi9vdXRwdXQvcmZfdHJhaW5fZmluYWxfYmFsYW5jZWQuUkRhdGEiKQogICAgbG9hZCgiLi4vb3V0cHV0L3JmX3RyYWluX2ZpbmFsX3RpbWVfYmFsYW5jZWQuUkRhdGEiKQogIH0KICAjIEV2YWx1YXRpb246CiAgaWYodGVzdC5yZil7CiAgICByZl9kYXRfdGVzdCA8LSBkYXRfdGVzdAogICAgcmZfZGF0X3Rlc3QkbGFiZWwgPC0gYXMubnVtZXJpYyhyZl9kYXRfdGVzdCRsYWJlbCkKCiAgICB0aW1lLnJmLnRlc3QuZmluYWwuYmFsYW5jZWQgPC0gc3lzdGVtLnRpbWUoCiAgICAgIHJmX3ByZWRpY3RlZF9iYWxhbmNlZCA8LSBhcy5udW1lcmljKGFzLnZlY3RvcihyYW5kb21fZm9yZXN0X3Rlc3QocmFuZG9tX2ZvcmVzdF9maXRfZmluYWxfYmFsYW5jZWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmZfZGF0X3Rlc3QpKSkpCiAgICByZl9hY2N1cmFjeV9iYWxhbmNlZCA8LSBtZWFuKHJvdW5kKHJmX3ByZWRpY3RlZF9iYWxhbmNlZCA9PSByZl9kYXRfdGVzdCRsYWJlbCkpCiAgICB0cHIuZnByLmJhbGFuY2VkIDwtIFdlaWdodGVkUk9DKGFzLm51bWVyaWMocmZfcHJlZGljdGVkX2JhbGFuY2VkKSxyZl9kYXRfdGVzdCRsYWJlbCkKICAgIHJmX0FVQ19iYWxhbmNlZCA8LSBXZWlnaHRlZEFVQyh0cHIuZnByLmJhbGFuY2VkKQoKICAgIGNhdCgiQVVDIGZvciB0dW5lZCBSYW5kb20gRm9yZXN0KGJhbGFuY2VkKTogIiwgcmZfQVVDX2JhbGFuY2VkLCIuXG4iKQogICAgY2F0KCJBY2N1cmFjeSBmb3IgdHVuZWQgUmFuZG9tIEZvcmVzdChiYWxhbmNlZCkiLCByZl9hY2N1cmFjeV9iYWxhbmNlZCoxMDAsIiUuXG4iKQogICAgY2F0KCJUcmFpbmluZyB0aW1lIGZvciB0dW5lZCBSYW5kb20gRm9yZXN0OiAiLCB0aW1lLnJmLnRyYWluLmZpbmFsLmJhbGFuY2VkWzFdLCAicy5cbiIpCiAgICBjYXQoIlRlc3RpbmcgdGltZSBmb3IgdHVuZWQgUmFuZG9tIEZvcmVzdDogIiwgdGltZS5yZi50ZXN0LmZpbmFsLmJhbGFuY2VkWzFdLCAicy5cbiIpCiAgICBjYXQoIiAgICIsIlxuIikKICB9Cn0KYGBgCgpXZSB0aGluayBSRiBtb2RlbCBkbyBub3QgbmVlZCBDcm9zcy1WYWxpZGF0aW9uLiBIZXJlIGlzIGEgc25pcHBldCBmcm9tIEJyZWltYW4ncyBvZmZpY2lhbCBkb2N1bWVudGF0aW9uOgpJbiByYW5kb20gZm9yZXN0cywgdGhlcmUgaXMgbm8gbmVlZCBmb3IgY3Jvc3MtdmFsaWRhdGlvbiBvciBhIHNlcGFyYXRlIHRlc3Qgc2V0IHRvIGdldCBhbiB1bmJpYXNlZCBlc3RpbWF0ZSBvZiB0aGUgdGVzdCBzZXQgZXJyb3IuIEl0IGlzIGVzdGltYXRlZCBpbnRlcm5hbGx5LCBkdXJpbmcgdGhlIHJ1biwgYXMgZm9sbG93czogRWFjaCB0cmVlIGlzIGNvbnN0cnVjdGVkIHVzaW5nIGEgZGlmZmVyZW50IGJvb3RzdHJhcCBzYW1wbGUgZnJvbSB0aGUgb3JpZ2luYWwgZGF0YS4gQWJvdXQgb25lLXRoaXJkIG9mIHRoZSBjYXNlcyBhcmUgbGVmdCBvdXQgb2YgdGhlIGJvb3RzdHJhcCBzYW1wbGUgYW5kIG5vdCB1c2VkIGluIHRoZSBjb25zdHJ1Y3Rpb24gb2YgdGhlIGt0aCB0cmVlLiBQdXQgZWFjaCBjYXNlIGxlZnQgb3V0IGluIHRoZSBjb25zdHJ1Y3Rpb24gb2YgdGhlIGt0aCB0cmVlIGRvd24gdGhlIGt0aCB0cmVlIHRvIGdldCBhIGNsYXNzaWZpY2F0aW9uLiBJbiB0aGlzIHdheSwgYSB0ZXN0IHNldCBjbGFzc2lmaWNhdGlvbiBpcyBvYnRhaW5lZCBmb3IgZWFjaCBjYXNlIGluIGFib3V0IG9uZS10aGlyZCBvZiB0aGUgdHJlZXMuIEF0IHRoZSBlbmQgb2YgdGhlIHJ1biwgdGFrZSBqIHRvIGJlIHRoZSBjbGFzcyB0aGF0IGdvdCBtb3N0IG9mIHRoZSB2b3RlcyBldmVyeSB0aW1lIGNhc2UgbiB3YXMgb29iLiBUaGUgcHJvcG9ydGlvbiBvZiB0aW1lcyB0aGF0IGogaXMgbm90IGVxdWFsIHRvIHRoZSB0cnVlIGNsYXNzIG9mIG4gYXZlcmFnZWQgb3ZlciBhbGwgY2FzZXMgaXMgdGhlIG9vYiBlcnJvciBlc3RpbWF0ZS4gVGhpcyBoYXMgcHJvdmVuIHRvIGJlIHVuYmlhc2VkIGluIG1hbnkgdGVzdHMuCgoKCgojIEFsdGVybmF0aXZlIE1vZGVsIDE6IFNWTSBNb2RlbAoKKiBCYWxhbmNlIHRoZSBUcmFpbmluZyBTZXQKCklmIHRoZSBnaXZlbiBkYXRhIHNldCBpcyBpbWJhbGFuY2VkLCB3ZSBhcHBseSBTTU9URSB0byByZWJhbGFuY2UgdGhlIGRhdGEgYW5kIHVzZSBpdCB0byB0cmFpbiBvdXIgc3ZtIG1vZGVscy4KCmBgYHtyfQppZihydW4uc3ZtKXsKICBpZihydW4uc3ZtLnRyYWluKXsKICAgIHRtX3N2bV9yZWJhbGFuY2VkX3RyYWluIDwtIE5BCiAgICBpZihzYW1wbGUucmV3ZWlnaHQpewogICAgICBkYXRfdHJhaW4kbGFiZWwgPSBhcy5mYWN0b3IoZGF0X3RyYWluJGxhYmVsKQogICAgICB0bV9zdm1fcmViYWxhbmNlZF90cmFpbiA8LSBzeXN0ZW0udGltZShzdm1fdHJhaW5pbmdfZGF0YSA8LSBTTU9URShsYWJlbCB+IC4sIGRhdGEgPSBkYXRfdHJhaW4pKQogICAgICBzYXZlKHRtX3N2bV9yZWJhbGFuY2VkX3RyYWluLCBmaWxlPSIuLi9vdXRwdXQvdG1fc3ZtX3JlYmFsYW5jZWRfdHJhaW4uUkRhdGEiKQogICAgfSBlbHNlIHsKICAgICAgc3ZtX3RyYWluaW5nX2RhdGEgPC0gZGF0X3RyYWluCiAgICAgIHRtX3N2bV9yZWJhbGFuY2VkX3RyYWluIDwtIHRtX2ZlYXR1cmVfdHJhaW4KICAgIH0KICB9Cn0KYGBgCgoqIE1vZGVsIFNlbGVjdGlvbgoKVHVuaW5nIGh5cGVyLXBhcmFtZXRlcnMgZm9yIGJvdGggbGluZWFyIGFuZCByYWRpYWwgYmFzaXMga2VybmVsIGFuZCBzZWxlY3QgdGhlIGtlcm5lbCBtZXRob2QgdGhhdCBwcm9kdWNlcyB0aGUgaGlnaGVzdCBBVUMgYW5kIGFjY3VyYWN5IGFtb25nIHRoZSB0d28gbWV0aG9kcy4KCmBgYHtyfQppZihydW4uc3ZtKXsKICBzZXQuc2VlZCgyMDIwKQogIGlmKG1vZGVsLnNlbGVjdGlvbil7CiAgICBzdm1fbW9kZWxfYXVjIDwtIHJlcChOQSwgMikKICAgIHN2bV9tb2RlbF9hY2N1IDwtIHJlcChOQSwgMikKICAgICMjIyBsaW5lYXIga2VybmVsCiAgICBpZihydW4uY3YpewogICAgICBiZXN0LmxpbmVhci5jb3N0IDwtIHN2bV9saW5lYXJfY29zdF90dW5lKHN2bV90cmFpbmluZ19kYXRhKQogICAgICBjYXQoIlRoZSBiZXN0IGNvc3QgZm9yIHN2bSBtb2RlbCB3aXRoIGxpbmVhciBrZXJuZWwgaXM6ICIsCiAgICAgICAgICBiZXN0LmxpbmVhci5jb3N0JGJlc3QucGFyYW1ldGVycyRjb3N0KSAjIGJlc3QgY29zdCBpcyAwLjAxCiAgICAgIHN2bS5saW5lYXIudHJhaW4uc3RhcnQgPSBwcm9jLnRpbWUoKQogICAgICBzdm1fbGluZWFyX21vZCA8LSBzdm1fbGluZWFyX3RyYWluKHN2bV90cmFpbmluZ19kYXRhLCAwLjAxLCBLKQogICAgICBzdm0ubGluZWFyLnRyYWluLmVuZCA9IHByb2MudGltZSgpCiAgICAgIHN2bS5saW5lYXIudG0gPSBzdm0ubGluZWFyLnRyYWluLmVuZCAtIHN2bS5saW5lYXIudHJhaW4uc3RhcnQKICAgICAgc2F2ZShzdm1fbGluZWFyX21vZCwgZmlsZT0iLi4vb3V0cHV0L3N2bV9saW5lYXJfbW9kLlJEYXRhIikKICAgICAgc2F2ZShzdm0ubGluZWFyLnRtLCBmaWxlPSIuLi9vdXRwdXQvdG1fc3ZtX2xpbmVhcl9tb2QuUkRhdGEiKQogICAgfSBlbHNlIHsKICAgICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvc3ZtX2xpbmVhcl9tb2QuUkRhdGEiKQogICAgfQogICAgc3ZtX2xpbmVhcl9wcmVkIDwtIHN2bV90ZXN0KHN2bV9saW5lYXJfbW9kLCBzdm1fdHJhaW5pbmdfZGF0YSwgRkFMU0UpCiAgICAjIGV2YWx1YXRlIHBlcmZvcm1hbmNlIG9uIGxpbmVhciBrZXJuZWwKICAgIHN2bV9tb2RlbF9hY2N1WzFdIDwtIG1lYW4ocm91bmQoc3ZtX2xpbmVhcl9wcmVkID09IHN2bV90cmFpbmluZ19kYXRhJGxhYmVsKSkKICAgIHRwci5mcHJfbGluZWFyIDwtIFdlaWdodGVkUk9DKGFzLm51bWVyaWMoc3ZtX2xpbmVhcl9wcmVkKSwgc3ZtX3RyYWluaW5nX2RhdGEkbGFiZWwpCiAgICBzdm1fbW9kZWxfYXVjWzFdIDwtIFdlaWdodGVkQVVDKHRwci5mcHJfbGluZWFyKQoKICAgICMjIyMgcmFkaWFsIGJhc2lzIGtlcm5lbAogICAgaWYocnVuLmN2KXsKICAgICAgYmVzdC5yYWRpYWwuY29zdCA8LSBzdm1fcmFkaWFsX2Nvc3RfdHVuZShzdm1fdHJhaW5pbmdfZGF0YSkKICAgICAgc3ZtLnJiZi50cmFpbi5zdGFydCA9IHByb2MudGltZSgpCiAgICAgIHN2bV9yYWRpYWxfbW9kIDwtIHN2bV9yYWRpYWxfdHJhaW4oc3ZtX3RyYWluaW5nX2RhdGEsIGJlc3QucmFkaWFsLmNvc3QsIEspCiAgICAgIHN2bS5yYmYudHJhaW4uZW5kID0gcHJvYy50aW1lKCkKICAgICAgc3ZtLnJiZi50bSA9IHN2bS5yYmYudHJhaW4uZW5kIC0gc3ZtLnJiZi50cmFpbi5zdGFydAogICAgICBzYXZlKHN2bV9yYWRpYWxfbW9kLCBmaWxlPSIuLi9vdXRwdXQvc3ZtX3JhZGlhbF9tb2QuUkRhdGEiKQogICAgICBzYXZlKHN2bS5yYmYudG0sIGZpbGU9Ii4uL291dHB1dC90bV9zdm1fcmFkaWFsX21vZC5SRGF0YSIpCiAgICB9IGVsc2UgewogICAgICBsb2FkKGZpbGU9Ii4uL291dHB1dC9zdm1fcmFkaWFsX21vZC5SRGF0YSIpCiAgICB9CiAgICBzdm1fcmFkaWFsX3ByZWQgPC0gc3ZtX3Rlc3Qoc3ZtX3JhZGlhbF9tb2QsIHN2bV90cmFpbmluZ19kYXRhLCBGQUxTRSkKICAgICMgZXZhbHVhdGUgcGVyZm9ybWFuY2Ugb24gcmJmIGtlcm5lbAogICAgc3ZtX21vZGVsX2FjY3VbMl0gPC0gbWVhbihyb3VuZChzdm1fcmFkaWFsX3ByZWQgPT0gc3ZtX3RyYWluaW5nX2RhdGEkbGFiZWwpKQogICAgdHByLmZwcl9yYmYgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhzdm1fcmFkaWFsX3ByZWQpLCBzdm1fdHJhaW5pbmdfZGF0YSRsYWJlbCkKICAgIHN2bV9tb2RlbF9hdWNbMl0gPC0gV2VpZ2h0ZWRBVUModHByLmZwcl9yYmYpCgogICAgIyB0YWJsZSB0byBkaXNwbGF5IHJlc3VsdHMgZm9yIHRoZSB0d28ga2VybmVsIG1ldGhvZHMKICAgIHN2bV9yZXMgPSBtYXRyaXgocmVwKE5BLDYpLG5jb2w9MykKICAgIHN2bV9yZXNbLDFdID0gc3ZtX21vZGVsX2FjY3UKICAgIHN2bV9yZXNbLDJdID0gc3ZtX21vZGVsX2F1YwogICAgc3ZtX3Jlc1ssM10gPSBjKHN2bS5saW5lYXIudG1bWzNdXSwgc3ZtLnJiZi50bVtbM11dKQogICAgY29sbmFtZXMoc3ZtX3JlcykgPSBjKCJBY2N1cmFjeSIsICJBVUMiLCJSdW5uaW5nIFRpbWUiKQogICAgcm93bmFtZXMoc3ZtX3JlcykgPSBjKCJsaW5lYXIiLCJyYWRpYWwgYmFzaXMiKQogICAgc2F2ZShzdm1fcmVzLCBmaWxlPSIuLi9vdXRwdXQvc3ZtX21vZGVsX3NlbGVjdGlvbi5SRGF0YSIpCiAgfSBlbHNlIHsKICAgIGlmKHJ1bi5wcmVzZW50YXRpb24uZGF5KXsKICAgICAgdG1fc3ZtX3JhZGlhbF9tb2QgPCBzeXN0ZW0udGltZShzdm1fcmFkaWFsX21vZCA8LSBzdm1fcmFkaWFsX3RyYWluKHN2bV90cmFpbmluZ19kYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMSwgSykpCiAgICB9IGVsc2UgewogICAgICBsb2FkKGZpbGU9Ii4uL291dHB1dC9zdm1fcmFkaWFsX21vZC5SRGF0YSIpCiAgICAgIGxvYWQoZmlsZT0iLi4vb3V0cHV0L3N2bV9tb2RlbF9zZWxlY3Rpb24uUkRhdGEiKQogICAgICBzdm1fcmVzCiAgICB9CiAgfQp9CmBgYAoKClNpbmNlIHJhZGlhbCBiYXNpcyBrZXJuZWwgaGFzIGhpZ2hlciBhY2N1cmFjeSBhbmQgQVVDIHRoYW4gbGluZWFyIGtlcm5lbCwgd2Ugd2lsbCBjaG9vc2UgcmFkaWFsIGJhc2lzIGFzIG91ciBrZXJuZWwgbWV0aG9kIGZvciB0cmFpbmluZyB0aGUgc3ZtIG1vZGVsLgoKICAqIEV2YWx1YXRpb24gb24gVGVzdGluZyBEYXRhCgpgYGB7cn0KaWYocnVuLnN2bSl7CiAgdG1fc3ZtX3JiZl90ZXN0IDwtIE5BCiAgc3ZtX3Rlc3RpbmdfZGF0YSA8LSBkYXRfdGVzdAogIGlmKHJ1bi5zdm0udGVzdCl7CiAgICBpZighcnVuLnByZXNlbnRhdGlvbi5kYXkpIHsKICAgICAgdG1fc3ZtX3JiZl90ZXN0IDwtIHN5c3RlbS50aW1lKHN2bV9yYmZfcHJlZCA8LSBzdm1fdGVzdChzdm1fcmFkaWFsX21vZCwgc3ZtX3Rlc3RpbmdfZGF0YSwgRkFMU0UpKQogICAgICBzdm1fdGVzdF9hY2N1ID0gbWVhbihyb3VuZChzdm1fcmJmX3ByZWQgPT0gc3ZtX3Rlc3RpbmdfZGF0YSRsYWJlbCkpCiAgICAgIHRwci5mcHIucmJmIDwtIFdlaWdodGVkUk9DKGFzLm51bWVyaWMoc3ZtX3JiZl9wcmVkKSwgc3ZtX3Rlc3RpbmdfZGF0YSRsYWJlbCkKICAgICAgc3ZtX3Rlc3RfYXVjID0gV2VpZ2h0ZWRBVUModHByLmZwci5yYmYpCiAgICAgIHNhdmUodG1fc3ZtX3JiZl90ZXN0LCBmaWxlPSIuLi9vdXRwdXQvdG1fc3ZtX3JiZl90ZXN0LlJEYXRhIikKICAKICAgICAgY2F0KCJUaGUgYWNjdXJhY3kgb2Ygc3ZtIG1vZGVsIGlzIiwgc3ZtX3Rlc3RfYWNjdSoxMDAsICIlLlxuIikKICAgICAgY2F0KCJUaGUgQVVDIG9mIHN2bSBtb2RlbCBpcyIsIHN2bV90ZXN0X2F1YywgIi5cbiIpCiAgICB9IGVsc2UgewogICAgICB0bV9zdm1fcmJmX3Rlc3QgPC0gc3lzdGVtLnRpbWUoc3ZtX3JiZl9wcmVkIDwtIHN2bV90ZXN0KHN2bV9yYWRpYWxfbW9kLCBzdm1fdGVzdGluZ19kYXRhKSkKICAgICAgc3ZtX3Rlc3RfYWNjdSA9IG1lYW4ocm91bmQoc3ZtX3JiZl9wcmVkID09IHN2bV90ZXN0aW5nX2RhdGEkbGFiZWwpKQogICAgICB0cHIuZnByLnJiZiA8LSBXZWlnaHRlZFJPQyhhcy5udW1lcmljKHN2bV9yYmZfcHJlZCksIHN2bV90ZXN0aW5nX2RhdGEkbGFiZWwpCiAgICAgIHN2bV90ZXN0X2F1YyA9IFdlaWdodGVkQVVDKHRwci5mcHIucmJmKQogICAgICBzYXZlKHRtX3N2bV9yYmZfdGVzdCwgZmlsZT0iLi4vb3V0cHV0L3RtX3N2bV9yYmZfdGVzdC5SRGF0YSIpCiAgCiAgICAgIGNhdCgiVGhlIGFjY3VyYWN5IG9mIHN2bSBtb2RlbCBpcyIsIHN2bV90ZXN0X2FjY3UqMTAwLCAiJS5cbiIpCiAgICAgIGNhdCgiVGhlIEFVQyBvZiBzdm0gbW9kZWwgaXMiLCBzdm1fdGVzdF9hdWMsICIuXG4iKQogICAgfQogIH0KfQpgYGAKCgoqIFN1bW1hcml6ZSBSdW5uaW5nIFRpbWUKCmBgYHtyfQppZihydW4uc3ZtKXsKICBjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdCB0cmFpbmluZyBmZWF0dXJlcyA9IiwgdG1fZmVhdHVyZV90cmFpblsxXSwgInMgXG4iKQogIGNhdCgiVGltZSBmb3IgY29uc3RydWN0IHRlc3RpbmcgZmVhdHVyZXMgPSIsIHRtX2ZlYXR1cmVfdGVzdFsxXSwgInMgXG4iKQogIGNhdCgiVGltZSBmb3IgdHJhaW5pbmcgc3ZtIG1vZGVsID0iLCBzdm1fcmVzWzIsM10sICJzIFxuIikKICBjYXQoIlRpbWUgZm9yIHRlc3Rpbmcgc3ZtIG1vZGVsPSIsIHRtX3N2bV9yYmZfdGVzdFsxXSwgInMgXG4iKQp9CmBgYAoKCgojIEFsdGVybmF0aXZlIE1vZGVsIDI6IFJpZGdlIE1vZGVsCgoqIEFwcGx5IENvbnN0cnVjdGVkIFJpZGdlIE1vZGVsIHRvIHRoZSBUcmFpbmluZyBTZXQKCmBgYHtyfQppZihydW4ucmlkZ2UpewogIHRtX3JpZGdlX3RyYWluIDwtIE5BCiAgaWYgKHRyYWluLnJpZGdlKXsKICAgIGRhdF90cmFpbl9yZWJhbGFuY2VkIDwtIFJPU0UobGFiZWwgfiAuLCBkYXRhID0gZGF0X3RyYWluLCBzZWVkPTIwMjApJGRhdGEKICAgIHRtX3JpZGdlX3RyYWluIDwtIHN5c3RlbS50aW1lKHJpZGdlX2N2X21vZGVsPC1yaWRnZV90cmFpbih0cmFpbl9kYXRhPWRhdF90cmFpbl9yZWJhbGFuY2VkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhPWFscGhhLCBLPUssIGxhbWJkYT1sYW1iZGEpKQogICAgc2F2ZShyaWRnZV9jdl9tb2RlbCwgZmlsZT0iLi4vb3V0cHV0L3JpZGdlX2N2X21vZGVsLlJEYXRhIikKICAgIHNhdmUodG1fcmlkZ2VfdHJhaW4sIGZpbGU9Ii4uL291dHB1dC9yaWRnZV90cmFpbl90aW1lLlJEYXRhIikKICB9IGVsc2UgewogICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvcmlkZ2VfY3ZfbW9kZWwuUkRhdGEiKQogICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvcmlkZ2VfdHJhaW5fdGltZS5SRGF0YSIpCiAgfQp9CmBgYAoKKiBVc2UgQ3Jvc3MtVmFsaWRhdGlvbiB0byBDaG9vc2UgdGhlIE9wdGltYWwgTGFtYmRhIHdpdGggU21hbGxlc3QgTVNFCgpgYGB7cn0KaWYocnVuLnJpZGdlKXsKICBpZiAodHJhaW4ucmlkZ2UpewogICAgc2V0LnNlZWQoMjAyMCkKICAgIGRhdF90cmFpbl9yZWJhbGFuY2VkIDwtIFJPU0UobGFiZWwgfiAuLCBkYXRhID0gZGF0X3RyYWluLCBzZWVkPTIwMjApJGRhdGEKICAgIGZlYXR1cmVfdHJhaW4gPSBhcy5tYXRyaXgoZGF0X3RyYWluX3JlYmFsYW5jZWRbLC1kaW0oZGF0X3RyYWluX3JlYmFsYW5jZWQpWzJdXSkKICAgIGxhYmVsX3RyYWluID0gYXMuaW50ZWdlcihkYXRfdHJhaW5fcmViYWxhbmNlZCRsYWJlbCkKICAgIHJpZGdlX21vZGVsID0gY3YuZ2xtbmV0KHg9ZmVhdHVyZV90cmFpbiwgeT1sYWJlbF90cmFpbiwgYWxwaGE9YWxwaGEsIG5mb2xkcz1LLCBsYW1iZGE9bGFtYmRhKQogICAgb3B0X2xhbWJkYSA9IHJpZGdlX21vZGVsJGxhbWJkYS5taW4KICAgIHNhdmUob3B0X2xhbWJkYSwgZmlsZT0iLi4vb3V0cHV0L3JpZGdlX29wdGltYWxfbGFtYmRhLlJEYXRhIikKICB9IGVsc2UgewogICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvcmlkZ2Vfb3B0aW1hbF9sYW1iZGEuUkRhdGEiKQogIH0KfQpgYGAKCiogUHJlZGljdCBvbiBUZXN0aW5nIFNldCB3aXRoIHRoZSBPcHRpbWFsIExhbWJkYQoKYGBge3J9CmlmKHJ1bi5yaWRnZSl7CiAgdG1fcmlkZ2VfdGVzdCA9IE5BCiAgaWYocnVuLnRlc3QpewogICAgbG9hZCgiLi4vb3V0cHV0L3JpZGdlX2N2X21vZGVsLlJEYXRhIikKICAgIGRhdF90ZXN0X3JlYmFsYW5jZWQgPC0gZGF0X3Rlc3QKICAgIGZlYXR1cmVfdGVzdCA8LSBhcy5tYXRyaXgoZGF0X3Rlc3RfcmViYWxhbmNlZFssIC1kaW0oZGF0X3Rlc3RfcmViYWxhbmNlZClbMl1dKQogICAgdG1fcmlkZ2VfdGVzdCA8LSBzeXN0ZW0udGltZShsYWJlbF9wcmVkPC1hcy5pbnRlZ2VyKHJpZGdlX3Rlc3QobW9kZWw9cmlkZ2VfY3ZfbW9kZWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmZWF0dXJlcz1mZWF0dXJlX3Rlc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmVkLnR5cGUgPSAnY2xhc3MnKSkpCiAgICBzYXZlKHRtX3JpZGdlX3Rlc3QsIGZpbGU9Ii4uL291dHB1dC9yaWRnZV90ZXN0X3RpbWUuUkRhdGEiKQogIH0gZWxzZXsKICAgIGxvYWQoZmlsZT0iLi4vb3V0cHV0L3JpZGdlX3Rlc3RfdGltZS5SRGF0YSIpCiAgfQp9CmBgYAoKKiBTdW1tYXJpemUgUnVubmluZyBUaW1lCgpgYGB7cn0KaWYocnVuLnJpZGdlKXsKICBjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0cmFpbmluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3RyYWluWzFdLCAicyBcbiIpCiAgY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdGVzdGluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3Rlc3RbMV0sICJzIFxuIikKICBjYXQoIlRpbWUgZm9yIHRyYWluaW5nIHJpZGdlIG1vZGVsPSIsIHRtX3JpZGdlX3RyYWluWzFdLCAicyBcbiIpCiAgY2F0KCJUaW1lIGZvciB0ZXN0aW5nIHJpZGdlIG1vZGVsPSIsIHRtX3JpZGdlX3Rlc3RbMV0sICJzIFxuIikKfQpgYGAKCiogRXZhbHVhdGlvbiBvbiBJbmRlcGVuZGVudCBUZXN0aW5nIERhdGEKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQppZihydW4ucmlkZ2UpewogIGxvYWQoIi4uL291dHB1dC9yaWRnZV9jdl9tb2RlbC5SRGF0YSIpCiAgZGF0X3Rlc3RfcmViYWxhbmNlZCA8LSBkYXRfdGVzdAogIGZlYXR1cmVfdGVzdCA8LSBhcy5tYXRyaXgoZGF0X3Rlc3RfcmViYWxhbmNlZFssIC1kaW0oZGF0X3Rlc3RfcmViYWxhbmNlZClbMl1dKQogIGxhYmVsX3ByZWQgPSBhcy5pbnRlZ2VyKHByZWRpY3QocmlkZ2VfY3ZfbW9kZWwsIHM9b3B0X2xhbWJkYSwgbmV3eD1mZWF0dXJlX3Rlc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlPSdjbGFzcycpKQogIGxhYmVsX3Rlc3QgPSBhcy5pbnRlZ2VyKGRhdF90ZXN0X3JlYmFsYW5jZWQkbGFiZWwpCiAgcmlkZ2VfYWNjdXJhY3kgPSBtZWFuKHJvdW5kKGxhYmVsX3Rlc3Q9PSBsYWJlbF9wcmVkKSkKICBjYXQoIlRoZSBhY2N1cmFjeSBvZiB0aGUgcmlkZ2UgbW9kZWwgaXMiLCByaWRnZV9hY2N1cmFjeSoxMDAsICIlLlxuIikKICByaWRnZV9BVUMgPSBhdWMocm9jKGxhYmVsX3ByZWQsbGFiZWxfdGVzdCkpCiAgY2F0KCJUaGUgQVVDIG9mIHRoZSByaWRnZSBtb2RlbCBpcyIsIHJpZGdlX0FVQywgIi5cbiIpCn0KYGBgCgoKCiMgQWx0ZXJuYXRpdmUgTW9kZWwgMzogUENBICsgTERBCgoqIFJlYmFsYW5jZSBUcmFpbmluZyBTZXQKCmBgYHtyfQppZihydW4ucGNhX2xkYSl7CiAgaWYocnVuLmxkYS50cmFpbil7CiAgICBpZihzYW1wbGUucmV3ZWlnaHQpewogICAgICBkYXRfdHJhaW4kbGFiZWwgPC0gYXMuZmFjdG9yKGRhdF90cmFpbiRsYWJlbCkKICAgICAgYmFsYW5jZWRfdHJhaW5fZGF0YSA8LSBTTU9URShsYWJlbH4uLGRhdGEgPSBkYXRfdHJhaW4pCiAgICAgIHNhdmUoYmFsYW5jZWRfdHJhaW5fZGF0YSwgZmlsZT0iLi4vb3V0cHV0L2ZlYXR1cmVfYmFsYW5jZWRfdHJhaW4uUkRhdGEiKQogICAgfSBlbHNlIHsKICAgICAgbG9hZChiYWxhbmNlZF90cmFpbl9kYXRhLCBmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90cmFpbi5SRGF0YSIpCiAgICB9CiAgfQp9CmBgYAoKKiBQZXJmb3JtIFBDQSBmb3IgRGltZW5zaW9uIFJlZHVjdGlvbgoKKipTaW5jZSB0aGVyZSBhcmUgb3ZlciA2MDAwIGZlYXR1cmVzLCB3ZSBpbXBsZW1lbnQgdGhlIFBDQSBtZXRob2QgdG8gcmVkdWNlIGRpbWVuc2lvbiBhY2NvcmRpbmcgdG8gdGhlIGNvdmFyaWFuY2UgbWF0cml4LiBXZSBvbmx5IHJldGFpbiBQQ3Mgd2l0aCBsYXJnZSB2YXJpYW5jZS4qKgoKYGBge3IgcGNhIGxkYX0KaWYocnVuLnBjYV9sZGEpewogIGJhbGFuY2VkX3Rlc3RfZGF0YSA8LSBkYXRfdGVzdAogIGlmKHJ1bi5zZWxlY3RfUEMpewogICAgI3NlcGFyYXRlIHRoZSBmZWF0dXJlcyBmcm9tIGxhYmVsCiAgICBkYXRfdHJhaW5fbmV3IDwtIGJhbGFuY2VkX3RyYWluX2RhdGFbLC1kaW0oYmFsYW5jZWRfdHJhaW5fZGF0YSlbMl1dCiAgICBkYXRfdGVzdF9uZXcgPC0gYmFsYW5jZWRfdGVzdF9kYXRhWywtZGltKGJhbGFuY2VkX3Rlc3RfZGF0YSlbMl1dCiAgICAjY3JlYXRlIGEgdmVjdG9yIGNvbnRhaW4gdGFyZ2V0IG51bWJlciBvZiBQQ3MKICAgIG51bS5wY2EgPC0gYygxMCw1MCw1MDAsMTAwMCkKICAgIHRyYWluX3BjYSA8LSBmdW5jdGlvbihudW0ucGNhKXsKICAgICAgZm9yKGkgaW4gMTpsZW5ndGgobnVtLnBjYSkpewogICAgICAgICNzdGFydCB0aW1lIGZvciB0cmFpbmluZyB0aGUgbW9kZWwKICAgICAgICB0cmFpbi5tb2RlbC5zdGFydCA9IHByb2MudGltZSgpCiAgICAgICAgI3J1biBQQ0EKICAgICAgICBwY2EgPC0gcHJjb21wKGRhdF90cmFpbl9uZXcpCiAgICAgICAgI3N0b3JlIGZvciBlYWNoIHBvdGVudGlhbCBQQwogICAgICAgIHRyYWluX3BjYSA8LSBkYXRhLmZyYW1lKHBjYSR4WywxOm51bS5wY2FbaV1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gYmFsYW5jZWRfdHJhaW5fZGF0YVtkaW0oYmFsYW5jZWRfdHJhaW5fZGF0YSlbMl1dKQogICAgICAgIHByZWRfcGNhIDwtIHByZWRpY3QocGNhLGRhdF90ZXN0X25ldykKICAgICAgICB0ZXN0X3BjYSA8LSBkYXRhLmZyYW1lKHByZWRfcGNhWywxOm51bS5wY2FbaV1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBiYWxhbmNlZF90ZXN0X2RhdGFbZGltKGJhbGFuY2VkX3Rlc3RfZGF0YSlbMl1dKQogICAgICAgICNmaXR0aW5nIHRoZSBsZGEgbW9kZWwKICAgICAgICBsZGFfcGNhIDwtIGxkYShsYWJlbCB+IC4sIGRhdGEgPSB0cmFpbl9wY2EpCiAgICAgICAgI3N0b3AgdGltZSBmb3IgdHJhaW5pbmcgdGhlIG1vZGVsCiAgICAgICAgdHJhaW4ubW9kZWwuZW5kID0gcHJvYy50aW1lKCkKICAgICAgICAjc3RhcnQgdGltZSBmb3IgdGVzdGluZyB0aGUgbW9kZWwKICAgICAgICB0ZXN0Lm1vZGVsLnN0YXJ0ID0gcHJvYy50aW1lKCkKICAgICAgICAjcHJlZGljdCBsZGEgbW9kZWwKICAgICAgICBsZGFfcHJlZF9wY2EgPSBwcmVkaWN0KGxkYV9wY2EsdGVzdF9wY2FbLWRpbSh0ZXN0X3BjYSlbMl1dKQogICAgICAgICNlbmQgdGltZSBmb3IgdGVzdGluZyB0aGUgbW9kZWwKICAgICAgICB0ZXN0Lm1vZGVsLmVuZCA9IHByb2MudGltZSgpCiAgICAgICAgI3Rlc3QgYWNjdXJhY3kKICAgICAgICB0ZXN0X2FjY3VyYWN5PWNvbmZ1c2lvbk1hdHJpeChsZGFfcHJlZF9wY2EkY2xhc3MsIHRlc3RfcGNhJGxhYmVsKSRvdmVyYWxsWzFdCiAgICAgICAgcHJpbnQobGlzdChsMT10cmFpbi5tb2RlbC5lbmQgLSB0cmFpbi5tb2RlbC5zdGFydCwKICAgICAgICAgICAgICAgICAgIGwyPXRlc3QubW9kZWwuZW5kIC0gdGVzdC5tb2RlbC5zdGFydCwKICAgICAgICAgICAgICAgICAgIGwzPXRlc3RfYWNjdXJhY3kpKQogICAgICB9CiAgICB9CiAgICB0cmFpbl9wY2EobnVtLnBjYSkKICB9Cn0KYGBgCgoqKkJ5IGNvbXBhcmluZyB0aGUgdHJhaW5pbmcgdGltZSwgdGVzdCB0aW1lIGFuZCBhY2N1cmFjeSwgd2UgdXNlIG1vZGVsIHdpdGggNTAwIFBDcy4qKgoKICAqIE1vZGVsIFRyYWluaW5nCgpgYGB7ciBmaW5hbCBtb2RlbH0KaWYocnVuLnBjYV9sZGEpewogIHRyYWluLm1vZGVsLnN0YXJ0ID0gcHJvYy50aW1lKCkKICBpZihydW4ubGRhLnRyYWluKXsKICAgIHBjYV81MDAgPC0gcHJjb21wKGJhbGFuY2VkX3RyYWluX2RhdGFbLC1kaW0oYmFsYW5jZWRfdHJhaW5fZGF0YSlbMl1dKQogICAgdHJhaW5fcGNhXzUwMCA8LSBkYXRhLmZyYW1lKHBjYV81MDAkeFssMTo1MDBdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gYmFsYW5jZWRfdHJhaW5fZGF0YVtkaW0oYmFsYW5jZWRfdHJhaW5fZGF0YSlbMl1dKQogICAgcHJlZF9wY2FfNTAwIDwtIHByZWRpY3QocGNhXzUwMCxiYWxhbmNlZF90ZXN0X2RhdGFbLC1kaW0oYmFsYW5jZWRfdGVzdF9kYXRhKVsyXV0pCiAgICB0ZXN0X3BjYV81MDAgPC0gZGF0YS5mcmFtZShwcmVkX3BjYV81MDBbLDE6NTAwXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gYmFsYW5jZWRfdGVzdF9kYXRhW2RpbShiYWxhbmNlZF90ZXN0X2RhdGEpWzJdXSkKICAgIHNhdmUodHJhaW5fcGNhXzUwMCwgZmlsZT0iLi4vb3V0cHV0L2ZlYXR1cmVfcGNhX3RyYWluLlJEYXRhIikKICAgIHNhdmUodGVzdF9wY2FfNTAwLCBmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV9wY2FfdGVzdC5SRGF0YSIpICAKICB9IGVsc2UgewogICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV9wY2FfdHJhaW4uUkRhdGEiKQogICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV9wY2FfdGVzdC5SRGF0YSIpICAKICB9CiAgI2NhbGN1bGF0ZSB0aGUgdHJhaW5pbmcgdGltZQogIGxkYV9wY2FfNTAwIDwtIGxkYShsYWJlbCB+IC4sIGRhdGEgPSB0cmFpbl9wY2FfNTAwLCBjdiA9IFRSVUUpCiAgdHJhaW4ubW9kZWwuZW5kID0gcHJvYy50aW1lKCkKfQpgYGAKCiogQ2FsY3VsYXRlIHRoZSBUcmFpbmluZyBhbmQgVGVzdGluZyBBY2N1cmFjeSBvZiBMREEgTW9kZWwKCmBgYHtyfQppZihydW4ucGNhX2xkYSl7CiAgdGVzdC5tb2RlbC5zdGFydCA9IHByb2MudGltZSgpCiAgcHJlZF90cmFpbl9sZGEgPC0gcHJlZGljdChsZGFfcGNhXzUwMCwgdHJhaW5fcGNhXzUwMFstZGltKHRyYWluX3BjYV81MDApWzJdXSkKICBhY2N1X3RyYWluX2xkYSA8LSBtZWFuKHByZWRfdHJhaW5fbGRhJGNsYXNzID09IHRyYWluX3BjYV81MDAkbGFiZWwpCiAgY2F0KCJUaGUgdHJhaW5pZyBhY2N1cmFjeSBvZiBtb2RlbDogTERBIiwgImlzIiwgYWNjdV90cmFpbl9sZGEqMTAwLCAiJS5cbiIpCiAgI2NhbGN1bGF0aW5nIHRoZSB0ZXN0IHRpbWUKICBpZihydW4udGVzdCl7CiAgICBwcmVkX3Rlc3RfbGRhIDwtIHByZWRpY3QobGRhX3BjYV81MDAsIHRlc3RfcGNhXzUwMCkKICB9CiAgdGVzdC5tb2RlbC5lbmQgPSBwcm9jLnRpbWUoKQogIHNhdmUocHJlZF90ZXN0X2xkYSwgZmlsZT0iLi4vb3V0cHV0L2ZpdF90cmFpbi5SRGF0YSIpCiAgYWNjdV90ZXN0X2xkYSA8LSBtZWFuKHByZWRfdGVzdF9sZGEkY2xhc3MgPT0gdGVzdF9wY2FfNTAwJGxhYmVsKQogIGNhdCgiVGhlIGFjY3VyYWN5IG9mIG1vZGVsOiBMREEiLCAiaXMiLCBhY2N1X3Rlc3RfbGRhKjEwMCwgIiUuXG4iKQogIHRwci5mcHIgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhwcmVkX3Rlc3RfbGRhJGNsYXNzKSwgdGVzdF9wY2FfNTAwJGxhYmVsKQogIGxkYV9hdWMgPSBXZWlnaHRlZEFVQyh0cHIuZnByKQogIGNhdCgiVGhlIEFVQyBvZiBtb2RlbDogTERBIGlzIiwgbGRhX2F1YywgIi5cbiIpCn0KYGBgCgoqIFN1bW1hcml6ZSBSdW5uaW5nIFRpbWUKClByZWRpY3Rpb24gcGVyZm9ybWFuY2UgbWF0dGVycywgc28gZG9lcyB0aGUgcnVubmluZyB0aW1lcyBmb3IgY29uc3RydWN0aW5nIGZlYXR1cmVzIGFuZCBmb3IgdHJhaW5pbmcgdGhlIG1vZGVsLCBlc3BlY2lhbGx5IHdoZW4gdGhlIGNvbXB1dGF0aW9uIHJlc291cmNlIGlzIGxpbWl0ZWQuCgpgYGB7cn0KaWYocnVuLnBjYV9sZGEpewogIHRtX3RyYWluIDwtIHRyYWluLm1vZGVsLmVuZCAtIHRyYWluLm1vZGVsLnN0YXJ0CiAgdG1fdGVzdCA8LSB0ZXN0Lm1vZGVsLmVuZCAtIHRlc3QubW9kZWwuc3RhcnQKICBjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0cmFpbmluZyBmZWF0dXJlcyA9IiwgdG1fZmVhdHVyZV90cmFpblsxXSwgInMgXG4iKQogIGNhdCgiVGltZSBmb3IgY29uc3RydWN0aW5nIHRlc3RpbmcgZmVhdHVyZXMgPSIsIHRtX2ZlYXR1cmVfdGVzdFsxXSwgInMgXG4iKQogIGNhdCgiVGltZSBmb3IgdHJhaW5pbmcgbW9kZWwgPSIsIHRtX3RyYWluWzFdLCAicyBcbiIpCiAgY2F0KCJUaW1lIGZvciB0ZXN0aW5nIG1vZGVsID0iLCB0bV90ZXN0WzFdLCAicyBcbiIpCn0KYGBgCgoKCiMgIEFsdGVybmF0aXZlIE1vZGVsIDQ6IFJhbmRvbSBGb3Jlc3QgKFJGKSBNb2RlbCB3aXRoIG9sZCBmZWF0dXJlczoKClRoZSBmb3VydGggYWx0ZXJuYXRpdmUgbW9kZWwgaXMgcmFuZG9tIGZvcmVzdCB1c2luZyBvbGQgZmVhdHVyZXMgZ2l2ZW4gaW4gdGhlIHN0YXJ0ZXIgY29kZS4gSGVyZSB3ZSB1c2UgdGhlIGRhdGFzZXRzIHRoYXQgYXJlIGV4dHJhY3RlZCBieSBvbGQgZmVhdHVyZSBmdW5jdGlvbnMuIFdlIHVzZWQgdHdvIG1vZGVscyB0cmFpbmVkIGJ5IGJvdGggaW1iYWxhbmNlZCBhbmQgYmFsYW5jZWQgZGF0YXNldC4gV2UgdXNlZCBST1NFIGZ1bmN0aW9uIHRvIGJhbGFuY2UgYm90aCB0cmFpbmluZyBhbmQgdGVzdGluZyBkYXRhLiBGb3IgdHVuaW5nIHRoZSBtb2RlbCwgd2Ugc2V0IG10cnkgPSAzMDgsIHRyZWUgbnVtYmVyID0gNTAwLCBhbmQgbm9kZSBzaXplID0gMTAgZm9yIHRoZSBSRiBtb2RlbCB1c2luZyBiYWxhbmNlZCBkYXRhLCBhbmQgd2Ugc2V0IG10cnkgPSAzMDgsIHRyZWUgbnVtYmVyID0gMTUwMCwgYW5kIG5vZGUgc2l6ZSA9IDMwIGZvciB0aGUgUkYgbW9kZWwgdXNpbmcgaW1iYWxhbmNlZCBkYXRhLiBUaGUgZXZhbHVhdGlvbiBvZiB0aGUgbW9kZWwgaXMgc2hvd24gYXQgdGhlIGVuZCBvZiB0aGlzIHNlY3Rpb24sIGFuZCB3ZSB3aWxsIGNvbXBhcmUgdGhpcyBtb2RlbCB3aXRoIHRoZSBSRiBtb2RlbCB0cmFpbmVkIGJ5IG5ldyBmZWF0dXJlcy4KCmBgYHtyfQppZihydW4ucmYub2xkLmZlYXR1cmUpewogIG9sZF9mZWF0dXJlIDwtIGZ1bmN0aW9uKGlucHV0X2xpc3QgPSBmaWR1Y2lhbF9wdF9saXN0LCBpbmRleCl7CiAgICBvbGRfcGFpcndpc2VfZGlzdCA8LSBmdW5jdGlvbih2ZWMpewogICAgICByZXR1cm4oYXMudmVjdG9yKGRpc3QodmVjKSkpCiAgICB9CiAgICBvbGRfcGFpcndpc2VfZGlzdF9yZXN1bHQgPC1mdW5jdGlvbihtYXQpewogICAgICByZXR1cm4oYXMudmVjdG9yKGFwcGx5KG1hdCwgMiwgb2xkX3BhaXJ3aXNlX2Rpc3QpKSkKICAgIH0KICAgIG9sZF9wYWlyd2lzZV9kaXN0X2ZlYXR1cmUgPC0gdChzYXBwbHkoaW5wdXRfbGlzdFtpbmRleF0sIG9sZF9wYWlyd2lzZV9kaXN0X3Jlc3VsdCkpCiAgICBkaW0ob2xkX3BhaXJ3aXNlX2Rpc3RfZmVhdHVyZSkKICAgIG9sZF9wYWlyd2lzZV9kYXRhIDwtIGNiaW5kKG9sZF9wYWlyd2lzZV9kaXN0X2ZlYXR1cmUsIGluZm8kbGFiZWxbaW5kZXhdKQogICAgY29sbmFtZXMob2xkX3BhaXJ3aXNlX2RhdGEpIDwtIGMocGFzdGUoImZlYXR1cmUiLCAxOihuY29sKG9sZF9wYWlyd2lzZV9kYXRhKS0xKSwgc2VwID0gIiIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImxhYmVsIikKICAgIG9sZF9wYWlyd2lzZV9kYXRhIDwtIGFzLmRhdGEuZnJhbWUob2xkX3BhaXJ3aXNlX2RhdGEpCiAgICBvbGRfcGFpcndpc2VfZGF0YSRsYWJlbCA8LSBhcy5mYWN0b3Iob2xkX3BhaXJ3aXNlX2RhdGEkbGFiZWwpCiAgICByZXR1cm4oZmVhdHVyZV9kZiA9IG9sZF9wYWlyd2lzZV9kYXRhKQogIH0KCiAgb2xkX3RtX2ZlYXR1cmVfdHJhaW4gPC0gTkEKICBpZihydW4uZmVhdHVyZS50cmFpbil7CiAgICBvbGRfdG1fZmVhdHVyZV90cmFpbiA8LSBzeXN0ZW0udGltZShvbGRfZGF0X3RyYWluIDwtIG9sZF9mZWF0dXJlKGZpZHVjaWFsX3B0X2xpc3QsIHRyYWluX2lkeCkpCiAgICBzYXZlKG9sZF9kYXRfdHJhaW4sIGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3RyYWluX29sZC5SRGF0YSIpCiAgfWVsc2V7CiAgICBsb2FkKGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3RyYWluX29sZC5SRGF0YSIpCiAgfQogIG9sZF90bV9mZWF0dXJlX3Rlc3QgPC0gTkEKICBpZihydW4uZmVhdHVyZS50ZXN0KXsKICAgIG9sZF90bV9mZWF0dXJlX3Rlc3QgPC0gc3lzdGVtLnRpbWUob2xkX2RhdF90ZXN0IDwtIG9sZF9mZWF0dXJlKGZpZHVjaWFsX3B0X2xpc3QsIHRlc3RfaWR4KSkKICAgIHNhdmUob2xkX2RhdF90ZXN0LCBmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90ZXN0X29sZC5SRGF0YSIpCiAgfWVsc2V7CiAgICBsb2FkKGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3Rlc3Rfb2xkLlJEYXRhIikKICB9CgogICMgVHJhaW4gTW9kZWwKICBpZih0cmFpbi5yZi5vbGQuZmVhdHVyZSl7CiAgICAjIHRyYW5zZmVyIGxhYmVsIGNvbHVtbiBmcm9tIGZhY3RvciB0byBudW1lcmljCiAgICBvbGRfZGF0X3RyYWluJGxhYmVsIDwtIGFzLm51bWVyaWMob2xkX2RhdF90cmFpbiRsYWJlbCkKICAgIG9sZF9kYXRfdGVzdCRsYWJlbCA8LSBhcy5udW1lcmljKG9sZF9kYXRfdGVzdCRsYWJlbCkKICAKICAgICMgQmFsYW5jZSBkYXRhCiAgICBkYXRfdHJhaW4kbGFiZWwgPC0gYXMuZmFjdG9yKGRhdF90cmFpbiRsYWJlbCkKICAgIG9sZF9kYXRfdHJhaW5fYmFsYW5jZWRfU01PVEUgPC0gU01PVEUobGFiZWwgfiAuLCBkYXRfdHJhaW4sIHBlcmMub3ZlciA9IDEwMCwgcGVyYy51bmRlcj0yMDApCiAgICAjIEJhbGFuY2VkCiAgICBvbGRfdGltZS5yZi50cmFpbi5maW5hbC5iYWxhbmNlZCA8LSBzeXN0ZW0udGltZSgKICAgICAgb2xkX3JhbmRvbV9mb3Jlc3RfZml0X2ZpbmFsX2JhbGFuY2VkIDwtIG9sZF9yYW5kb21fZm9yZXN0X3RyYWluKG9sZF9kYXRfdHJhaW5fYmFsYW5jZWRfU01PVEUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gMzA4LCB0cmVlX251bWJlciA9IDEwMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBub2RlX3NpemUgPSAzMCkpCiAgICBzYXZlKG9sZF9yYW5kb21fZm9yZXN0X2ZpdF9maW5hbF9iYWxhbmNlZCwKICAgICAgICAgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fZmluYWxfYmFsYW5jZWRfb2xkX2ZlYXR1cmUuUkRhdGEiKQogICAgc2F2ZShvbGRfdGltZS5yZi50cmFpbi5maW5hbC5iYWxhbmNlZCwKICAgICAgICAgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fZmluYWxfdGltZV9iYWxhbmNlZF9vbGRfZmVhdHVyZS5SRGF0YSIpCiAgIAogIH1lbHNlewogICAgbG9hZCgiLi4vb3V0cHV0L3JmX3RyYWluX2ZpbmFsX2JhbGFuY2VkX29sZF9mZWF0dXJlLlJEYXRhIikKICAgIGxvYWQoIi4uL291dHB1dC9yZl90cmFpbl9maW5hbF90aW1lX2JhbGFuY2VkX29sZF9mZWF0dXJlLlJEYXRhIikKICB9CgoKICAjIEV2YWx1YXRlIE1vZGVsCiAgb2xkX3JmX2RhdF90ZXN0IDwtIG9sZF9kYXRfdGVzdAogIG9sZF9yZl9kYXRfdGVzdCRsYWJlbCA8LSBhcy5udW1lcmljKG9sZF9yZl9kYXRfdGVzdCRsYWJlbCkKICBpZih0ZXN0LnJmLm9sZC5mZWF0dXJlKXsKICAgIG9sZF90aW1lLnJmLnRlc3QuZmluYWwuYmFsYW5jZWQgPC0gc3lzdGVtLnRpbWUoCiAgICAgIHJmX3ByZWRpY3RlZF9iYWxhbmNlZCA8LSBhcy5udW1lcmljKGFzLnZlY3RvcihvbGRfcmFuZG9tX2ZvcmVzdF90ZXN0KG9sZF9yYW5kb21fZm9yZXN0X2ZpdF9maW5hbF9iYWxhbmNlZCwgb2xkX3JmX2RhdF90ZXN0KSkpKQogICAgb2xkX3JmX2FjY3VyYWN5X2JhbGFuY2VkIDwtIG1lYW4ocm91bmQocmZfcHJlZGljdGVkX2JhbGFuY2VkID09IG9sZF9yZl9kYXRfdGVzdCRsYWJlbCkpCiAgICBvbGRfdHByLmZwci5iYWxhbmNlZCA8LSBXZWlnaHRlZFJPQyhhcy5udW1lcmljKHJmX3ByZWRpY3RlZF9iYWxhbmNlZCksb2xkX3JmX2RhdF90ZXN0JGxhYmVsKQogICAgb2xkX3JmX0FVQ19iYWxhbmNlZCA8LSBXZWlnaHRlZEFVQyhvbGRfdHByLmZwci5iYWxhbmNlZCkKCiAgICBjYXQoIkFVQyhiYWxhbmNlZCkgZm9yIFJhbmRvbSBGb3Jlc3Qgd2l0aCBvbGQgZmVhdHVyZTogIiwgCiAgICAgICAgb2xkX3JmX0FVQ19iYWxhbmNlZCwiLlxuIikKICAgIGNhdCgiQWNjdXJhY3koYmFsYW5jZWQpIGZvciBSYW5kb20gRm9yZXN0IHdpdGggb2xkIGZlYXR1cmUiLCAKICAgICAgICBvbGRfcmZfYWNjdXJhY3lfYmFsYW5jZWQqMTAwLCIlLlxuIikKICAgIGNhdCgiVHJhaW5pbmcgdGltZSAoYmFsYW5jZWQpIGZvciBSYW5kb20gRm9yZXN0IHdpdGggb2xkIGZlYXR1cmU6ICIsIAogICAgICAgIG9sZF90aW1lLnJmLnRyYWluLmZpbmFsLmJhbGFuY2VkWzFdLCAicy5cbiIpCiAgICBjYXQoIlRlc3RpbmcgdGltZSAoYmFsYW5jZWQpIGZvciBSYW5kb20gRm9yZXN0IHdpdGggb2xkIGZlYXR1cmU6ICIsIAogICAgICAgIG9sZF90aW1lLnJmLnRlc3QuZmluYWwuYmFsYW5jZWRbMV0sICJzLlxuIikKICAgIGNhdCgiICAgIiwiXG4iKQogIH0KfQpgYGAKCiMgR2VuZXJhdGUgYSBjc3Ygb24gcHJlc2VudGF0aW9uIGRheQoKYGBge3IgZ2VuZXJhdGUgY3N2fQppZiAocnVuLnByZXNlbnRhdGlvbi5kYXkpewogIGNzdmZpbGVvdXRwdXQ8LSIuLi9vdXRwdXQvbGFiZWxfcHJlZGljdGlvbi5jc3YiCiAgQWR2YW5jZWQ8LXJmX3ByZWRpY3RlZF9iYWxhbmNlZAogIEJhc2VsaW5lPC1sYWJlbF9wcmVkX2Jhc2VsaW5lCiAgSW5kZXg8LXRlc3RfaWR4CiAgY3N2ZGF0YSA8LSBkYXRhLmZyYW1lKEluZGV4LCBCYXNlbGluZSwgQWR2YW5jZWQpCgogIHdyaXRlLmNzdihjc3ZkYXRhLGNzdmZpbGVvdXRwdXQsIHJvdy5uYW1lcz1GQUxTRSxxdW90ZSA9IEZBTFNFKQp9CmBgYAoKIyMjIFJlZmVyZW5jZQotIER1LCBTLiwgVGFvLCBZLiwgJiBNYXJ0aW5leiwgQS4gTS4gKDIwMTQpLiBDb21wb3VuZCBmYWNpYWwgZXhwcmVzc2lvbnMgb2YgZW1vdGlvbi4gUHJvY2VlZGluZ3Mgb2YgdGhlIE5hdGlvbmFsIEFjYWRlbXkgb2YgU2NpZW5jZXMsIDExMSgxNSksIEUxNDU0LUUxNDYyLgo=